Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: migrate to scala 3 #532

Merged
merged 13 commits into from
Nov 28, 2024
9 changes: 9 additions & 0 deletions .scalafmt.conf
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,12 @@ runner.dialect = scala3

align.preset = more
maxColumn = 110

rewrite {
scala3 {
convertToNewSyntax = true
removeOptionalBraces = true
insertEndMarkerMinLines = 30
removeEndMarkerMaxLines = 29
}
}
10 changes: 2 additions & 8 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import sbtcrossproject.CrossPlugin.autoImport.crossProject
import _root_.org.typelevel.sbt.tpolecat.TpolecatPlugin.autoImport._
import _root_.org.typelevel.scalacoptions.ScalacOptions

lazy val scalaVersion213 = "2.13.13"
lazy val scalaVersion3 = "3.3.3"
lazy val scalaVersions = List(scalaVersion213, scalaVersion3)
lazy val scalaVersion3 = "3.3.3"
lazy val scalaVersions = List(scalaVersion3)

inThisBuild(
Seq(
Expand Down Expand Up @@ -83,10 +81,6 @@ val docs = project
.dependsOn(coreJVM, mtlJVM)
.enablePlugins(MicrositesPlugin, BuildInfoPlugin)
.settings(
scalaVersion := scalaVersion213,
addCompilerPlugin(
"org.typelevel" %% "kind-projector" % "0.13.3" cross CrossVersion.full
),
tpolecatExcludeOptions ++= ScalacOptions.warnUnusedOptions,
tpolecatExcludeOptions += ScalacOptions.warnNonUnitStatement,
crossScalaVersions := Nil,
Expand Down
21 changes: 7 additions & 14 deletions modules/core/shared/src/main/scala/retry/Fibonacci.scala
Original file line number Diff line number Diff line change
@@ -1,24 +1,17 @@
package retry

object Fibonacci {
def fibonacci(n: Int): Long = {
if (n > 0)
fib(n)._1
else
0
}
object Fibonacci:
def fibonacci(n: Int): Long =
if n > 0 then fib(n)._1
else 0

// "Fast doubling" Fibonacci algorithm.
// See e.g. http://funloop.org/post/2017-04-14-computing-fibonacci-numbers.html for explanation.
private def fib(n: Int): (Long, Long) = n match {
private def fib(n: Int): (Long, Long) = n match
case 0 => (0, 1)
case m =>
val (a, b) = fib(m / 2)
val c = a * (b * 2 - a)
val d = a * a + b * b
if (n % 2 == 0)
(c, d)
else
(d, c + d)
}
}
if n % 2 == 0 then (c, d)
else (d, c + d)
12 changes: 3 additions & 9 deletions modules/core/shared/src/main/scala/retry/PolicyDecision.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,6 @@ package retry

import scala.concurrent.duration.FiniteDuration

sealed trait PolicyDecision

object PolicyDecision {
case object GiveUp extends PolicyDecision

final case class DelayAndRetry(
delay: FiniteDuration
) extends PolicyDecision
}
enum PolicyDecision:
case GiveUp
case DelayAndRetry(delay: FiniteDuration)
44 changes: 22 additions & 22 deletions modules/core/shared/src/main/scala/retry/RetryDetails.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,30 @@ package retry

import scala.concurrent.duration.FiniteDuration

sealed trait RetryDetails {
def retriesSoFar: Int
def cumulativeDelay: FiniteDuration
def givingUp: Boolean
def upcomingDelay: Option[FiniteDuration]
}
enum RetryDetails(
val retriesSoFar: Int,
val cumulativeDelay: FiniteDuration,
val givingUp: Boolean,
val upcomingDelay: Option[FiniteDuration]
):

object RetryDetails {
final case class GivingUp(
case GivingUp(
totalRetries: Int,
totalDelay: FiniteDuration
) extends RetryDetails {
val retriesSoFar: Int = totalRetries
val cumulativeDelay: FiniteDuration = totalDelay
val givingUp: Boolean = true
val upcomingDelay: Option[FiniteDuration] = None
}
) extends RetryDetails(
retriesSoFar = totalRetries,
cumulativeDelay = totalDelay,
givingUp = true,
upcomingDelay = None
)

final case class WillDelayAndRetry(
case WillDelayAndRetry(
nextDelay: FiniteDuration,
retriesSoFar: Int,
cumulativeDelay: FiniteDuration
) extends RetryDetails {
val givingUp: Boolean = false
val upcomingDelay: Option[FiniteDuration] = Some(nextDelay)
}
}
override val retriesSoFar: Int,
override val cumulativeDelay: FiniteDuration
) extends RetryDetails(
retriesSoFar = retriesSoFar,
cumulativeDelay = cumulativeDelay,
givingUp = false,
upcomingDelay = Some(nextDelay)
)
30 changes: 12 additions & 18 deletions modules/core/shared/src/main/scala/retry/RetryPolicies.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ package retry
import java.util.concurrent.TimeUnit

import cats.Applicative
import cats.syntax.functor._
import cats.syntax.show._
import retry.PolicyDecision._
import cats.syntax.functor.*
import cats.syntax.show.*
import retry.PolicyDecision.*

import scala.concurrent.duration.{Duration, FiniteDuration}
import scala.util.Random

object RetryPolicies {
object RetryPolicies:
private val LongMax: BigInt = BigInt(Long.MaxValue)

/*
Expand All @@ -24,12 +24,11 @@ object RetryPolicies {
private def safeMultiply(
duration: FiniteDuration,
multiplier: Long
): FiniteDuration = {
): FiniteDuration =
val durationNanos = BigInt(duration.toNanos)
val resultNanos = durationNanos * BigInt(multiplier)
val safeResultNanos = resultNanos min LongMax
FiniteDuration(safeResultNanos.toLong, TimeUnit.NANOSECONDS)
}

/** Don't retry at all and always give up. Only really useful for combining with other policies.
*/
Expand Down Expand Up @@ -66,11 +65,8 @@ object RetryPolicies {
def limitRetries[M[_]: Applicative](maxRetries: Int): RetryPolicy[M] =
RetryPolicy.liftWithShow(
{ status =>
if (status.retriesSoFar >= maxRetries) {
GiveUp
} else {
DelayAndRetry(Duration.Zero)
}
if status.retriesSoFar >= maxRetries then GiveUp
else DelayAndRetry(Duration.Zero)
},
show"limitRetries(maxRetries=$maxRetries)"
)
Expand Down Expand Up @@ -121,37 +117,35 @@ object RetryPolicies {
def limitRetriesByDelay[M[_]: Applicative](
threshold: FiniteDuration,
policy: RetryPolicy[M]
): RetryPolicy[M] = {
): RetryPolicy[M] =
def decideNextRetry(status: RetryStatus): M[PolicyDecision] =
policy.decideNextRetry(status).map {
case r @ DelayAndRetry(delay) =>
if (delay > threshold) GiveUp else r
if delay > threshold then GiveUp else r
case GiveUp => GiveUp
}

RetryPolicy.withShow[M](
decideNextRetry,
show"limitRetriesByDelay(threshold=$threshold, $policy)"
)
}

/** Add an upperbound to a policy such that once the cumulative delay over all retries has reached or
* exceeded the given limit, the policy will stop retrying and give up.
*/
def limitRetriesByCumulativeDelay[M[_]: Applicative](
threshold: FiniteDuration,
policy: RetryPolicy[M]
): RetryPolicy[M] = {
): RetryPolicy[M] =
def decideNextRetry(status: RetryStatus): M[PolicyDecision] =
policy.decideNextRetry(status).map {
case r @ DelayAndRetry(delay) =>
if (status.cumulativeDelay + delay >= threshold) GiveUp else r
if status.cumulativeDelay + delay >= threshold then GiveUp else r
case GiveUp => GiveUp
}

RetryPolicy.withShow[M](
decideNextRetry,
show"limitRetriesByCumulativeDelay(threshold=$threshold, $policy)"
)
}
}
end RetryPolicies
34 changes: 16 additions & 18 deletions modules/core/shared/src/main/scala/retry/RetryPolicy.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,20 @@ package retry

import cats.{Apply, Applicative, Monad, Functor}
import cats.kernel.BoundedSemilattice
import retry.PolicyDecision._
import retry.PolicyDecision.*

import scala.concurrent.duration.Duration
import scala.concurrent.duration.FiniteDuration
import cats.arrow.FunctionK
import cats.implicits._
import cats.implicits.*
import cats.Show

case class RetryPolicy[M[_]](
decideNextRetry: RetryStatus => M[PolicyDecision]
) {
):
def show: String = toString

def followedBy(rp: RetryPolicy[M])(implicit M: Apply[M]): RetryPolicy[M] =
def followedBy(rp: RetryPolicy[M])(using M: Apply[M]): RetryPolicy[M] =
RetryPolicy.withShow(
status =>
M.map2(decideNextRetry(status), rp.decideNextRetry(status)) {
Expand All @@ -29,7 +29,7 @@ case class RetryPolicy[M[_]](
* choosing the maximum of the two delays when both of the schedules want to delay the next retry. The dual
* of the `meet` operation.
*/
def join(rp: RetryPolicy[M])(implicit M: Apply[M]): RetryPolicy[M] =
def join(rp: RetryPolicy[M])(using M: Apply[M]): RetryPolicy[M] =
RetryPolicy.withShow[M](
status =>
M.map2(decideNextRetry(status), rp.decideNextRetry(status)) {
Expand All @@ -43,7 +43,7 @@ case class RetryPolicy[M[_]](
* choosing the minimum of the two delays when both of the schedules want to delay the next retry. The dual
* of the `join` operation.
*/
def meet(rp: RetryPolicy[M])(implicit M: Apply[M]): RetryPolicy[M] =
def meet(rp: RetryPolicy[M])(using M: Apply[M]): RetryPolicy[M] =
RetryPolicy.withShow[M](
status =>
M.map2(decideNextRetry(status), rp.decideNextRetry(status)) {
Expand All @@ -57,7 +57,7 @@ case class RetryPolicy[M[_]](

def mapDelay(
f: FiniteDuration => FiniteDuration
)(implicit M: Functor[M]): RetryPolicy[M] =
)(using M: Functor[M]): RetryPolicy[M] =
RetryPolicy.withShow(
status =>
M.map(decideNextRetry(status)) {
Expand All @@ -69,7 +69,7 @@ case class RetryPolicy[M[_]](

def flatMapDelay(
f: FiniteDuration => M[FiniteDuration]
)(implicit M: Monad[M]): RetryPolicy[M] =
)(using M: Monad[M]): RetryPolicy[M] =
RetryPolicy.withShow(
status =>
M.flatMap(decideNextRetry(status)) {
Expand All @@ -84,12 +84,12 @@ case class RetryPolicy[M[_]](
status => nt(decideNextRetry(status)),
show"$show.mapK(<FunctionK>)"
)
}
end RetryPolicy

object RetryPolicy {
object RetryPolicy:
def lift[M[_]](
f: RetryStatus => PolicyDecision
)(implicit
)(using
M: Applicative[M]
): RetryPolicy[M] =
RetryPolicy[M](decideNextRetry = retryStatus => M.pure(f(retryStatus)))
Expand All @@ -98,30 +98,28 @@ object RetryPolicy {
decideNextRetry: RetryStatus => M[PolicyDecision],
pretty: => String
): RetryPolicy[M] =
new RetryPolicy[M](decideNextRetry) {
new RetryPolicy[M](decideNextRetry):
override def show: String = pretty
override def toString: String = pretty
}

def liftWithShow[M[_]: Applicative](
decideNextRetry: RetryStatus => PolicyDecision,
pretty: => String
): RetryPolicy[M] =
withShow(rs => Applicative[M].pure(decideNextRetry(rs)), pretty)

implicit def boundedSemilatticeForRetryPolicy[M[_]](implicit
given [M[_]](using
M: Applicative[M]
): BoundedSemilattice[RetryPolicy[M]] =
new BoundedSemilattice[RetryPolicy[M]] {
new BoundedSemilattice[RetryPolicy[M]]:
override def empty: RetryPolicy[M] =
RetryPolicies.constantDelay[M](Duration.Zero)

override def combine(
x: RetryPolicy[M],
y: RetryPolicy[M]
): RetryPolicy[M] = x.join(y)
}

implicit def showForRetryPolicy[M[_]]: Show[RetryPolicy[M]] =
given [M[_]]: Show[RetryPolicy[M]] =
Show.show(_.show)
}
end RetryPolicy
6 changes: 2 additions & 4 deletions modules/core/shared/src/main/scala/retry/RetryStatus.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,12 @@ final case class RetryStatus(
retriesSoFar: Int,
cumulativeDelay: FiniteDuration,
previousDelay: Option[FiniteDuration]
) {
):
def addRetry(delay: FiniteDuration): RetryStatus = RetryStatus(
retriesSoFar = this.retriesSoFar + 1,
cumulativeDelay = this.cumulativeDelay + delay,
previousDelay = Some(delay)
)
}

object RetryStatus {
object RetryStatus:
val NoRetriesYet = RetryStatus(0, Duration.Zero, None)
}
10 changes: 4 additions & 6 deletions modules/core/shared/src/main/scala/retry/Sleep.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,11 @@ import cats.effect.Temporal

import scala.concurrent.duration.FiniteDuration

trait Sleep[M[_]] {
trait Sleep[M[_]]:
def sleep(delay: FiniteDuration): M[Unit]
}

object Sleep {
def apply[M[_]](implicit sleep: Sleep[M]): Sleep[M] = sleep
object Sleep:
def apply[M[_]](using sleep: Sleep[M]): Sleep[M] = sleep

implicit def sleepUsingTemporal[F[_]](implicit t: Temporal[F]): Sleep[F] =
given [F[_]](using t: Temporal[F]): Sleep[F] =
(delay: FiniteDuration) => t.sleep(delay)
}
Loading