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/.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/.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..28f0a0b 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -1,8 +1,101 @@ -version = "3.9.9" -runner.dialect = scala213 +# 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 fileOverride { - "glob:**/src/main/scala-3/**" { + "glob:**/scala-3/**" { runner.dialect = scala3 } -} \ No newline at end of file +} + +# 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 3ef73c1..08bc2b1 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") @@ -34,7 +31,7 @@ libraryDependencies ++= Seq( Cats.core, CatsEffect.effect, `cats-helper`, - scalatest % Test + scalatest % Test, ) autoAPIMappings := true @@ -46,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( @@ -55,18 +52,12 @@ developers := List( "t3hnar", "Yaroslav Klymko", "yklymko@evolution.com", - url("https://github.com/t3hnar") - ) + url("https://github.com/t3hnar"), + ), ) publishTo := Some(Resolver.evolutionReleases) -addCommandAlias( - "fmt", - "all scalafmtAll scalafmtSbt; scalafixEnable; scalafixAll" -) -addCommandAlias( - "check", - "all versionPolicyCheck Compile/doc scalafmtCheckAll scalafmtSbtCheck; scalafixEnable; scalafixAll --check" -) +addCommandAlias("fmt", "all scalafmtRepo") +addCommandAlias("check", "all versionPolicyCheck Compile/doc scalafmtCheckRepo") addCommandAlias("build", "+all compile test") diff --git a/project/Dependencies.scala b/project/Dependencies.scala index ce54af5..eda1e2c 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -1,17 +1,17 @@ -import sbt._ +import sbt.* object Dependencies { - val `cats-helper` = "com.evolutiongaming" %% "cats-helper" % "3.7.0" - val scalatest = "org.scalatest" %% "scalatest" % "3.2.19" + val `cats-helper` = "com.evolutiongaming" %% "cats-helper" % "3.12.2" + 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 } object CatsEffect { - private val version = "3.4.11" + private val version = "3.7.0" val effect = "org.typelevel" %% "cats-effect" % version } } 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 ac3d498..f5f307c 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,6 +1,4 @@ -addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.0.8") - -addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.14.3") +addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.4.4") addSbtPlugin("ch.epfl.scala" % "sbt-version-policy" % "3.3.0") @@ -10,6 +8,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") +addSbtPlugin("com.thoughtworks.sbt-api-mappings" % "sbt-api-mappings" % "3.0.3") 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 {