From 9c7127f4c5f46c015d16f7bf766978ff5afa2a91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mareks=20Ramp=C4=81ns?= <8796159+mr-git@users.noreply.github.com> Date: Mon, 10 Aug 2026 22:33:13 +0300 Subject: [PATCH 1/9] update scalatest to 3.2.20 --- project/Dependencies.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index ce54af5..f775b72 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -3,7 +3,7 @@ import sbt._ object Dependencies { val `cats-helper` = "com.evolutiongaming" %% "cats-helper" % "3.7.0" - val scalatest = "org.scalatest" %% "scalatest" % "3.2.19" + val scalatest = "org.scalatest" %% "scalatest" % "3.2.20" object Cats { private val version = "2.9.0" From 4b3e008eb0fe925f5061398282b85c201b48b225 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mareks=20Ramp=C4=81ns?= <8796159+mr-git@users.noreply.github.com> Date: Mon, 10 Aug 2026 22:34:27 +0300 Subject: [PATCH 2/9] update cats-effect to 3.7.0 --- project/Dependencies.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index f775b72..2d2654c 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -11,7 +11,7 @@ object Dependencies { } object CatsEffect { - private val version = "3.4.11" + private val version = "3.7.0" val effect = "org.typelevel" %% "cats-effect" % version } } From 74b04c26f6a8677271cf3b6f38ac23ec109d4c30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mareks=20Ramp=C4=81ns?= <8796159+mr-git@users.noreply.github.com> Date: Mon, 10 Aug 2026 22:34:55 +0300 Subject: [PATCH 3/9] update cats to 2.13.0 --- project/Dependencies.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 2d2654c..cbdba62 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -6,7 +6,7 @@ object Dependencies { val scalatest = "org.scalatest" %% "scalatest" % "3.2.20" object Cats { - private val version = "2.9.0" + private val version = "2.13.0" val core = "org.typelevel" %% "cats-core" % version } From ccd0aeadb480369267106919f32737e1cc811ef8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mareks=20Ramp=C4=81ns?= <8796159+mr-git@users.noreply.github.com> Date: Mon, 10 Aug 2026 22:35:17 +0300 Subject: [PATCH 4/9] update cats-helper to 3.12.2 --- project/Dependencies.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index cbdba62..de9073e 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -2,7 +2,7 @@ import sbt._ object Dependencies { - val `cats-helper` = "com.evolutiongaming" %% "cats-helper" % "3.7.0" + val `cats-helper` = "com.evolutiongaming" %% "cats-helper" % "3.12.2" val scalatest = "org.scalatest" %% "scalatest" % "3.2.20" object Cats { From 381b949f9850ddea637b9c664d3323e3d8d09a65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mareks=20Ramp=C4=81ns?= <8796159+mr-git@users.noreply.github.com> Date: Mon, 10 Aug 2026 22:37:33 +0300 Subject: [PATCH 5/9] update Scala to 3.3.8 and 2.13.18 (and drop 2.12 support) --- .github/workflows/ci.yml | 2 -- build.sbt | 7 ++----- project/Dependencies.scala | 2 +- 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e602b36..54a7b8a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,5 +8,3 @@ on: jobs: test: uses: evolution-gaming/scala-github-actions/.github/workflows/ci.yml@dde27b9bd793d41d5aacf8fb74403c9de5da1146 # v6.3.0 - with: - scala_versions: '["3.3.0", "2.13.11", "2.12.18"]' diff --git a/build.sbt b/build.sbt index 3ef73c1..815f061 100644 --- a/build.sbt +++ b/build.sbt @@ -8,13 +8,10 @@ organizationName := "Evolution" organizationHomepage := Some(url("https://evolution.com")) scalaVersion := crossScalaVersions.value.head -crossScalaVersions := Seq("2.13.11", "2.12.18", "3.3.5") +crossScalaVersions := Seq("2.13.18", "3.3.8") versionPolicyIntention := Compatibility.BinaryCompatible -scalacOptions += { - if (scalaVersion.value startsWith "2.12") "-Ywarn-unused-import" - else "-Wunused:imports" -} +scalacOptions += "-Wunused:imports" Compile / unmanagedSourceDirectories += { if (scalaVersion.value startsWith "2") diff --git a/project/Dependencies.scala b/project/Dependencies.scala index de9073e..eda1e2c 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -1,4 +1,4 @@ -import sbt._ +import sbt.* object Dependencies { From 8b91411b34802b9aadb2c17fe2432e365144da93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mareks=20Ramp=C4=81ns?= <8796159+mr-git@users.noreply.github.com> Date: Mon, 10 Aug 2026 22:38:33 +0300 Subject: [PATCH 6/9] update sbt-scalafmt to 2.6.2 --- build.sbt | 7 ++----- project/plugins.sbt | 2 +- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/build.sbt b/build.sbt index 815f061..56472d1 100644 --- a/build.sbt +++ b/build.sbt @@ -58,12 +58,9 @@ developers := List( publishTo := Some(Resolver.evolutionReleases) -addCommandAlias( - "fmt", - "all scalafmtAll scalafmtSbt; scalafixEnable; scalafixAll" -) +addCommandAlias("fmt", "all scalafmtRepo; scalafixEnable; scalafixAll") addCommandAlias( "check", - "all versionPolicyCheck Compile/doc scalafmtCheckAll scalafmtSbtCheck; scalafixEnable; scalafixAll --check" + "all versionPolicyCheck Compile/doc scalafmtCheckRepo; scalafixEnable; scalafixAll --check" ) addCommandAlias("build", "+all compile test") diff --git a/project/plugins.sbt b/project/plugins.sbt index ac3d498..5ab02d3 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -10,6 +10,6 @@ addSbtPlugin("com.evolution" % "sbt-artifactory-plugin" % "0.1.2") addSbtPlugin("com.github.sbt" % "sbt-dynver" % "5.1.1") -addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.5") +addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.6.2") addSbtPlugin("com.thoughtworks.sbt-api-mappings" % "sbt-api-mappings" % "3.0.2") From cefb61e8eb8cb70d5595c4946bc02ce5f1e94ee2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mareks=20Ramp=C4=81ns?= <8796159+mr-git@users.noreply.github.com> Date: Mon, 10 Aug 2026 22:40:28 +0300 Subject: [PATCH 7/9] drop scalafix --- .scalafix.conf | 6 --- .scalafmt.conf | 104 ++++++++++++++++++++++++++++++++++++++++---- build.sbt | 7 +-- project/plugins.sbt | 2 - 4 files changed, 98 insertions(+), 21 deletions(-) delete mode 100644 .scalafix.conf diff --git a/.scalafix.conf b/.scalafix.conf deleted file mode 100644 index 3d1bd2d..0000000 --- a/.scalafix.conf +++ /dev/null @@ -1,6 +0,0 @@ -rules = [OrganizeImports] - -OrganizeImports { - preset = INTELLIJ_2020_3 - targetDialect = Auto -} \ No newline at end of file diff --git a/.scalafmt.conf b/.scalafmt.conf index 16a83a0..d31bdbb 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -1,8 +1,96 @@ -version = "3.9.9" -runner.dialect = scala213 - -fileOverride { - "glob:**/src/main/scala-3/**" { - runner.dialect = scala3 - } -} \ No newline at end of file +# Main goals: +# - nicer commit diffs (trailing commas, no alignment for pattern matching, force new lines) +# - better interop with default IntelliJ IDEA setup (matching import and modifiers sorting logic) +# - better developer experience on laptop screens (like 16' MBPs) with IntelliJ IDEA (line wraps) + +version = 3.11.5 + +runner.dialect = scala213source3 + +# only format files tracked by git +project.git = true + +maxColumn = 120 +trailingCommas = always + +preset = default +# do not align to make nicer commit diffs +align.preset = none + +indent { + # altering defnSite and extendSite to have this: + # final class MyErr extends RuntimeException( + # "super error message", + # ) + # instead of this: + # final class MyErr extends RuntimeException( + # "super error message", + # ) + defnSite = 2 + extendSite = 0 +} + +spaces { + # makes string interpolation with curlies more visually distinct + inInterpolatedStringCurlyBraces = true +} + +newlines { + # keep author new lines where possible + source = keep + # force new line after "(implicit" for multi-line arg lists + implicitParamListModifierForce = [after] + avoidForSimpleOverflow = [ + tooLong, # if the line would be too long even after newline inserted, do nothing + slc, # do nothing if overflow caused by single line comment + ] +} + +verticalMultiline { + atDefnSite = true + arityThreshold = 4 # more than 3 args in a list will be turned vertical + newlineAfterOpenParen = true # for nicer commit diffs +} + +# for nicer commit diffs - forces new line before last parenthesis: +# class MyCls( +# arg1: String, +# arg2: String, +# ) extends MyTrait { +# +# without it: +# class MyCls( +# arg1: String, +# arg2: String) extends MyTrait { +danglingParentheses.exclude = [] + +docstrings { + # easier to view diffs in IDEA on 16' MBP screen if docs max line are shorter than code + wrapMaxColumn = 100 + # next settings make it similar to the default IDEA javadoc formatting + style = Asterisk + oneline = unfold + blankFirstLine = unfold +} + +rewrite.rules = [ + Imports, + RedundantParens, + SortModifiers, + prefercurlyfors, +] + +# put visibility modifier first +rewrite.sortModifiers.preset = styleGuide + +# Import sorting as similar as possible to scalafix's "OrganizeImports.preset = INTELLIJ_2020_3". +# Scalafix is not used as its commands mess up "all .." build aliases and it takes long time to run, +# while its code semantic based features are not needed here. +# I.e. detection of unused imports is done with Scala compiler options. +rewrite.imports { + sort = ascii + groups = [ + [".*"], + ["java\\..*", "javax\\..*", "scala\\..*"], + ] +} diff --git a/build.sbt b/build.sbt index 56472d1..957be5e 100644 --- a/build.sbt +++ b/build.sbt @@ -58,9 +58,6 @@ developers := List( publishTo := Some(Resolver.evolutionReleases) -addCommandAlias("fmt", "all scalafmtRepo; scalafixEnable; scalafixAll") -addCommandAlias( - "check", - "all versionPolicyCheck Compile/doc scalafmtCheckRepo; scalafixEnable; scalafixAll --check" -) +addCommandAlias("fmt", "all scalafmtRepo") +addCommandAlias("check", "all versionPolicyCheck Compile/doc scalafmtCheckRepo") addCommandAlias("build", "+all compile test") diff --git a/project/plugins.sbt b/project/plugins.sbt index 5ab02d3..08f1db1 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,7 +1,5 @@ addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.0.8") -addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.14.3") - addSbtPlugin("ch.epfl.scala" % "sbt-version-policy" % "3.3.0") addSbtPlugin("com.evolution" % "sbt-scalac-opts-plugin" % "0.2.0") From 27ca90d2f5634955850a15c417a903be59cdbee8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mareks=20Ramp=C4=81ns?= <8796159+mr-git@users.noreply.github.com> Date: Mon, 10 Aug 2026 22:45:39 +0300 Subject: [PATCH 8/9] update SBT to 1.12.15 --- .github/workflows/release.yml | 2 +- project/build.properties | 2 +- project/plugins.sbt | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e95abaf..2778c64 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,5 +7,5 @@ on: jobs: release: - uses: evolution-gaming/scala-github-actions/.github/workflows/release.yml@v3 + uses: evolution-gaming/scala-github-actions/.github/workflows/release.yml@v5 secrets: inherit \ No newline at end of file diff --git a/project/build.properties b/project/build.properties index 169b20f..1a59c2e 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.12.2 \ No newline at end of file +sbt.version = 1.12.15 diff --git a/project/plugins.sbt b/project/plugins.sbt index 08f1db1..f5f307c 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,4 +1,4 @@ -addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.0.8") +addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.4.4") addSbtPlugin("ch.epfl.scala" % "sbt-version-policy" % "3.3.0") @@ -10,4 +10,4 @@ addSbtPlugin("com.github.sbt" % "sbt-dynver" % "5.1.1") addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.6.2") -addSbtPlugin("com.thoughtworks.sbt-api-mappings" % "sbt-api-mappings" % "3.0.2") +addSbtPlugin("com.thoughtworks.sbt-api-mappings" % "sbt-api-mappings" % "3.0.3") From 56a6a83cbc7e51e6117c65347a7f0240fe9289aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mareks=20Ramp=C4=81ns?= <8796159+mr-git@users.noreply.github.com> Date: Mon, 10 Aug 2026 22:48:56 +0300 Subject: [PATCH 9/9] apply scalafmt --- .scalafmt.conf | 5 + build.sbt | 10 +- .../com/evolutiongaming/random/Random.scala | 103 +++++++++--------- .../com/evolutiongaming/random/Random.scala | 12 +- .../random/RandomStateOf.scala | 2 +- .../com/evolutiongaming/random/SeedOf.scala | 2 +- 6 files changed, 71 insertions(+), 63 deletions(-) diff --git a/.scalafmt.conf b/.scalafmt.conf index d31bdbb..28f0a0b 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -6,6 +6,11 @@ version = 3.11.5 runner.dialect = scala213source3 +fileOverride { + "glob:**/scala-3/**" { + runner.dialect = scala3 + } +} # only format files tracked by git project.git = true diff --git a/build.sbt b/build.sbt index 957be5e..08bc2b1 100644 --- a/build.sbt +++ b/build.sbt @@ -31,7 +31,7 @@ libraryDependencies ++= Seq( Cats.core, CatsEffect.effect, `cats-helper`, - scalatest % Test + scalatest % Test, ) autoAPIMappings := true @@ -43,8 +43,8 @@ Test / publishArtifact := false scmInfo := Some( ScmInfo( url("https://github.com/evolution-gaming/random"), - "git@github.com:evolution-gaming/random.git" - ) + "git@github.com:evolution-gaming/random.git", + ), ) developers := List( @@ -52,8 +52,8 @@ developers := List( "t3hnar", "Yaroslav Klymko", "yklymko@evolution.com", - url("https://github.com/t3hnar") - ) + url("https://github.com/t3hnar"), + ), ) publishTo := Some(Resolver.evolutionReleases) diff --git a/src/main/scala-2/com/evolutiongaming/random/Random.scala b/src/main/scala-2/com/evolutiongaming/random/Random.scala index f59d954..026d233 100644 --- a/src/main/scala-2/com/evolutiongaming/random/Random.scala +++ b/src/main/scala-2/com/evolutiongaming/random/Random.scala @@ -19,14 +19,17 @@ trait Random[F[_]] { object Random { - /** The type used as a seed for the random number generator. - * - * In this library it also used as an internal state of the random number - * generator. - */ + /** + * The type used as a seed for the random number generator. + * + * In this library it also used as an internal state of the random number generator. + */ type Seed = Long - def apply[F[_]](implicit F: Random[F]): Random[F] = F + def apply[F[_]]( + implicit + F: Random[F], + ): Random[F] = F implicit class RandomOps[F[_]](val self: Random[F]) extends AnyVal { @@ -42,33 +45,32 @@ object Random { } } - /** The pseudo random number generator (PRNG) for a single specific type `A` - * based on - * [[https://en.wikipedia.org/wiki/Linear_congruential_generator LCG]] - * algorithm. - * - * It takes some `state1` as an input and returns a new `state2` and a random - * value of type `A`. - * - * Technically, it is just a function from `(Seed)` to `(Seed, A)`. - * - * `StateT` is used instead of a plain function, it has the ability to chain - * several calls in for comprehensions, instead of doing something like - * following: - * ``` - * val (state1, a) = f(seed) - * val (state2, b) = g(state1) - * val (state3, c) = h(state2) - * ``` - * - * The practice shown that this introduces a lot of confusion, so in future - * library versions `StateT` will not be exposed in public API. - */ + /** + * The pseudo random number generator (PRNG) for a single specific type `A` based on + * [[https://en.wikipedia.org/wiki/Linear_congruential_generator LCG]] algorithm. + * + * It takes some `state1` as an input and returns a new `state2` and a random value of type `A`. + * + * Technically, it is just a function from `(Seed)` to `(Seed, A)`. + * + * `StateT` is used instead of a plain function, it has the ability to chain several calls in for + * comprehensions, instead of doing something like following: + * ``` + * val (state1, a) = f(seed) + * val (state2, b) = g(state1) + * val (state3, c) = h(state2) + * ``` + * + * The practice shown that this introduces a lot of confusion, so in future library versions + * `StateT` will not be exposed in public API. + */ type SeedT[A] = StateT[Id, Seed, A] object SeedT { - /** Set of random number generators for common numeric types */ + /** + * Set of random number generators for common numeric types + */ val Random: Random[SeedT] = { val doubleUnit = 1.0 / (1L << 53) @@ -118,21 +120,20 @@ object Random { StateT[Id, Seed, A] { seed => f(seed) } } - /** Snapshot of a state of a stateful random number generator. - * - * @param seed - * The internal state of the random number generator that will be used to - * generate the next random number. The initial `seed` is quite important - * as having `0` as seed reduces this LCG PRNG to lesser Lehmer RNG. - * Consider using [[State#fromClock]] for a good initial seed. - * @param random - * The stateless part of the random number generator, i.e. the set of - * functions from `state1` to `(state2, A)`, where `A` is the type of - * outputs of a random number generator such as `Int`, `Long`, `Float`, or - * `Double`. - */ + /** + * Snapshot of a state of a stateful random number generator. + * + * @param seed + * The internal state of the random number generator that will be used to generate the next + * random number. The initial `seed` is quite important as having `0` as seed reduces this LCG + * PRNG to lesser Lehmer RNG. Consider using [[State#fromClock]] for a good initial seed. + * @param random + * The stateless part of the random number generator, i.e. the set of functions from `state1` to + * `(state2, A)`, where `A` is the type of outputs of a random number generator such as `Int`, + * `Long`, `Float`, or `Double`. + */ final case class State(seed: Seed, random: Random[SeedT] = SeedT.Random) - extends Random[State.Type] { + extends Random[State.Type] { private def apply[A](stateT: SeedT[A]) = { val (seed1, a) = stateT.run(seed) @@ -152,19 +153,19 @@ object Random { type Type[A] = (State, A) - /** Create an instance of [[Random.State]] based on [[cats.effect.Clock]]. - * - * The state is initialized with a constant seed known to be good and mixed - * together with a current time to add an additional randomness. - */ + /** + * Create an instance of [[Random.State]] based on [[cats.effect.Clock]]. + * + * The state is initialized with a constant seed known to be good and mixed together with a + * current time to add an additional randomness. + */ def fromClock[F[_]: Clock: FlatMap]( - random: Random[SeedT] = SeedT.Random + random: Random[SeedT] = SeedT.Random, ): F[State] = for { nanos <- Clock[F].nanos } yield { - val seed = - (nanos ^ 3447679086515839964L ^ 0x5deece66dL) & ((1L << 48) - 1) + val seed = (nanos ^ 3447679086515839964L ^ 0x5deece66dL) & ((1L << 48) - 1) State(seed, random) } diff --git a/src/main/scala-3/com/evolutiongaming/random/Random.scala b/src/main/scala-3/com/evolutiongaming/random/Random.scala index ec2e025..3ee33cb 100644 --- a/src/main/scala-3/com/evolutiongaming/random/Random.scala +++ b/src/main/scala-3/com/evolutiongaming/random/Random.scala @@ -16,7 +16,10 @@ object Random { type Seed = Long - def apply[F[_]](using F: Random[F]): Random[F] = F + def apply[F[_]]( + using + F: Random[F], + ): Random[F] = F extension [F[_]](self: Random[F]) { def mapK[G[_]](f: F ~> G): Random[G] = new Random[G] { @@ -77,7 +80,7 @@ object Random { } final case class State(seed: Seed, random: Random[SeedT] = SeedT.Random) - extends Random[State.Type] { + extends Random[State.Type] { private def apply[A](stateT: SeedT[A]) = { val (seed1, a) = stateT.run(seed) @@ -95,12 +98,11 @@ object Random { type Type[A] = (State, A) def fromClock[F[_]: Clock: FlatMap]( - random: Random[SeedT] = SeedT.Random + random: Random[SeedT] = SeedT.Random, ): F[State] = { for nanos <- Clock[F].nanos yield - val seed = - (nanos ^ 3447679086515839964L ^ 0x5deece66dL) & ((1L << 48) - 1) + val seed = (nanos ^ 3447679086515839964L ^ 0x5deece66dL) & ((1L << 48) - 1) apply(seed, random) } } diff --git a/src/main/scala/com/evolutiongaming/random/RandomStateOf.scala b/src/main/scala/com/evolutiongaming/random/RandomStateOf.scala index ab8be14..ab7ef1b 100644 --- a/src/main/scala/com/evolutiongaming/random/RandomStateOf.scala +++ b/src/main/scala/com/evolutiongaming/random/RandomStateOf.scala @@ -9,7 +9,7 @@ trait RandomStateOf[F[_]] { object RandomStateOf { - private abstract sealed class Main + private sealed abstract class Main def of[F[_]: Sync]: F[RandomStateOf[F]] = { SeedOf diff --git a/src/main/scala/com/evolutiongaming/random/SeedOf.scala b/src/main/scala/com/evolutiongaming/random/SeedOf.scala index 395965e..f28c16a 100644 --- a/src/main/scala/com/evolutiongaming/random/SeedOf.scala +++ b/src/main/scala/com/evolutiongaming/random/SeedOf.scala @@ -9,7 +9,7 @@ trait SeedOf[F[_]] { } object SeedOf { - private abstract sealed class FromClock + private sealed abstract class FromClock def fromClock[F[_]: Sync]: F[SeedOf[F]] = { for {