Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]'
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

repo="https://github.com/evolution-gaming/scala-github-actions.git"
sha="$(git ls-remote "$repo" 'refs/tags/v5^{}' | awk '{print $1}')"
: "${sha:=$(git ls-remote "$repo" 'refs/tags/v5' | awk '{print $1}')}"
test -n "$sha"

echo "v5 resolves to $sha"
curl -fsSL "https://raw.githubusercontent.com/evolution-gaming/scala-github-actions/$sha/.github/workflows/release.yml"

Repository: evolution-gaming/random

Length of output: 2245


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

printf '%s\n' '--- caller workflow ---'
cat -n .github/workflows/release.yml

printf '%s\n' '--- repository references to the reusable workflow and secrets ---'
rg -n -C 3 'scala-github-actions|secrets:|JFROG_ACCESS_TOKEN|permissions:' .github/workflows .github 2>/dev/null || true

Repository: evolution-gaming/random

Length of output: 1573


🌐 Web query:

GitHub Actions reusable workflow secrets inherit secret not defined workflow_call contract

💡 Result:

In GitHub Actions, you can pass secrets to a reusable workflow using either explicit mapping or the inherit keyword [1][2]. When using secrets: inherit in the calling workflow, all secrets from the caller are implicitly passed to the reusable workflow [1][3]. A key behavior of this method is that it bypasses the need to explicitly define the secrets in the workflow_call trigger of the reusable workflow [1][4]. You can access these inherited secrets directly within the reusable workflow using the ${{ secrets.SECRET_NAME }} syntax, even if they are not listed in the on.workflow_call.secrets contract [1][2]. If you choose not to use secrets: inherit, you must explicitly pass each required secret in the calling workflow and define those secrets in the on.workflow_call.secrets block of the reusable workflow [5][6]. Important considerations regarding secret inheritance include: 1. Scope and Access: Secrets are only passed to directly called workflows. In a workflow chain (A > B > C), workflow C will only receive secrets from A if they are explicitly passed or inherited through each step in the chain [1][2]. 2. Environment Secrets: The on.workflow_call trigger does not support the environment keyword [1][7]. Environment secrets are not passed automatically from the caller; instead, if a reusable workflow job references an environment, it will use the environment secrets defined in the repository where the reusable workflow resides [1][7]. 3. Security: The inherit keyword provides a convenient way to share secrets, but ensure that the reusable workflow is intended to have access to all secrets available to the caller [1][3].

Citations:


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

repo="evolution-gaming/scala-github-actions"
tag="v5"
ref_json="$(curl -fsSL "https://api.github.com/repos/${repo}/git/refs/tags/${tag}")"
printf '%s\n' '--- tag object ---'
printf '%s\n' "$ref_json" | jq '{ref, object}'

type="$(printf '%s\n' "$ref_json" | jq -r '.object.type')"
object_sha="$(printf '%s\n' "$ref_json" | jq -r '.object.sha')"
if [ "$type" = "tag" ]; then
  commit_sha="$(curl -fsSL "https://api.github.com/repos/${repo}/git/tags/${object_sha}" | jq -r '.object.sha')
else
  commit_sha="$object_sha"
fi

printf 'resolved commit: %s\n' "$commit_sha"
printf '%s\n' '--- commit existence ---'
curl -fsSL "https://api.github.com/repos/${repo}/commits/${commit_sha}" | jq -r '.sha'

printf '%s\n' '--- v5 workflow at resolved commit ---'
curl -fsSL "https://raw.githubusercontent.com/${repo}/${commit_sha}/.github/workflows/release.yml"

Repository: evolution-gaming/random

Length of output: 212


🏁 Script executed:

#!/usr/bin/env bash
set -u

repo="evolution-gaming/scala-github-actions"
sha="b4557d9a82c03596dc2425e19b3cb9a6280a4739"

printf '%s\n' '--- commit API resolution ---'
if command -v gh >/dev/null 2>&1; then
  gh api "repos/${repo}/commits/${sha}" --jq '.sha' || true
else
  printf '%s\n' 'gh is unavailable'
fi

printf '%s\n' '--- git object resolution ---'
git ls-remote "https://github.com/${repo}.git" "$sha" "refs/tags/v5" "refs/tags/v5^{}" || true

printf '%s\n' '--- v5 workflow contract summary ---'
curl -fsSL "https://raw.githubusercontent.com/${repo}/${sha}/.github/workflows/release.yml" |
  awk '/workflow_call:/,/^jobs:/'

Repository: evolution-gaming/random

Length of output: 862


🌐 Web query:

site:docs.github.com/en/actions reusable workflow caller explicit secrets must be defined workflow_call secrets inherit GITHUB_TOKEN

💡 Result:

In GitHub Actions, secrets do not need to be explicitly defined in a reusable workflow's on.workflow_call block if you use the inherit keyword in the calling workflow [1]. When you use secrets: inherit in the caller workflow, all secrets available to the caller are implicitly passed to the reusable workflow, allowing you to reference them directly without prior definition in the reusable workflow's interface [1]. Regarding the GITHUB_TOKEN, it is important to note that it is automatically generated for every workflow job and is handled differently than user-defined secrets [2][3]. While the inherit keyword passes user-defined repository/environment secrets, the GITHUB_TOKEN is available by default in every job context regardless of secret inheritance [2][3]. You do not need to pass the GITHUB_TOKEN explicitly or via inheritance; it is always available, and you can reference it using ${{ secrets.GITHUB_TOKEN }} [3]. Summary of behavior: 1. Explicit definition: Required only if you are manually mapping specific secrets using the secrets keyword in the caller workflow [4][5]. 2. Implicit inheritance: If you use secrets: inherit, you do not need to define secrets in the reusable workflow's on.workflow_call block [1]. 3. GITHUB_TOKEN: This is not a standard repository secret; it is a built-in token automatically available to all workflow jobs [2]. It does not need to be defined in on.workflow_call or passed via inherit [2][3].

Citations:


🌐 Web query:

GitHub Actions reusable workflow secrets inherit GITHUB_TOKEN secrets context called workflow explicit mapping workflow_call

💡 Result:

In GitHub Actions, the GITHUB_TOKEN behaves differently than standard repository secrets when using reusable workflows. 1. Automatic Availability: The called (reusable) workflow is automatically granted access to the GITHUB_TOKEN without needing explicit mapping or inheritance [1]. You do not need to pass it via the secrets map or use secrets: inherit for the GITHUB_TOKEN to be available within the called workflow [1]. 2. Permissions: When a reusable workflow is triggered, it inherits the permissions associated with the GITHUB_TOKEN as defined by the caller job [1]. If the caller job does not specify permissions, the called workflow will have default permissions [1]. Importantly, any permissions specified in the called workflow can only be the same as or more restrictive (downgraded) than those provided by the caller; they cannot be elevated [1]. 3. Comparison with Other Secrets: Unlike the GITHUB_TOKEN, standard repository secrets are not automatically passed to reusable workflows [2]. To use them, you must either: - Explicitly map them using the secrets keyword in the job definition [3]: jobs: call_workflow: uses: path/to/reusable.yml secrets: MY_SECRET: ${{ secrets.MY_SECRET }} - Use the secrets: inherit keyword to pass all secrets available to the caller workflow to the called workflow [4][5]. For reusable workflows, remember that while GITHUB_TOKEN is always accessible, any other secrets required must be explicitly handled via mapping or inheritance to avoid "secret not defined" errors during the workflow parse phase [5].

Citations:


Pin the reusable workflow and narrow secret access.

Use commit b4557d9a82c03596dc2425e19b3cb9a6280a4739 instead of @v5. The workflow reads only JFROG_ACCESS_TOKEN besides the automatic GITHUB_TOKEN, but secrets: inherit passes all caller secrets. After v5 declares JFROG_ACCESS_TOKEN in its workflow_call contract, replace inheritance with explicit mapping.

🧰 Tools
🪛 zizmor (1.29.0)

[warning] 10-10: secrets unconditionally inherited by called workflow (secrets-inherit): this reusable workflow

(secrets-inherit)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/release.yml at line 10, Update the reusable workflow
reference in the release workflow to pin commit
b4557d9a82c03596dc2425e19b3cb9a6280a4739 instead of the v5 tag, and replace
secrets: inherit with an explicit mapping that passes only JFROG_ACCESS_TOKEN
while retaining the automatic GITHUB_TOKEN.

Sources: MCP tools, Linters/SAST tools

secrets: inherit
6 changes: 0 additions & 6 deletions .scalafix.conf

This file was deleted.

101 changes: 97 additions & 4 deletions .scalafmt.conf
Original file line number Diff line number Diff line change
@@ -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
}
}
}

# 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,
]
Comment on lines +81 to +86

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

curl -fsSL \
  https://raw.githubusercontent.com/scalameta/scalafmt/v3.11.5/scalafmt-core/shared/src/main/scala/org/scalafmt/rewrite/Rewrite.scala \
  | rg -n -C 2 'PreferCurlyFors|ConfCodecEx\.oneOf'

Repository: evolution-gaming/random

Length of output: 448


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- repository configuration ---'
sed -n '70,92p' .scalafmt.conf

printf '%s\n' '--- Scalafmt rewrite-rule decoding ---'
curl -fsSL \
  https://raw.githubusercontent.com/scalameta/scalafmt/v3.11.5/scalafmt-core/shared/src/main/scala/org/scalafmt/rewrite/Rewrite.scala \
  | sed -n '96,132p'

printf '%s\n' '--- documented rule name ---'
curl -fsSL \
  https://scalameta.org/scalafmt/docs/configuration.html \
  | rg -n -C 3 'PreferCurlyFors|prefercurlyfors'

Repository: evolution-gaming/random

Length of output: 50380


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- Scalafmt references in the repository ---'
rg -n -i --glob '!target/**' --glob '!node_modules/**' 'scalafmt|scalafmtVersion|scalafmt-core' . | head -80 || true

printf '%s\n' '--- Exact rule-name comparison ---'
python3 - <<'PY'
import re
import urllib.request
from pathlib import Path

source = urllib.request.urlopen(
    "https://raw.githubusercontent.com/scalameta/scalafmt/v3.11.5/"
    "scalafmt-core/shared/src/main/scala/org/scalafmt/rewrite/Rewrite.scala"
).read().decode()
config = Path(".scalafmt.conf").read_text()

registered = re.findall(r"^\s{4}([A-Z][A-Za-z0-9]+),\s*$", source, re.M)
configured = re.search(r"^\s*prefercurlyfors,\s*$", config, re.M)
print("registered_prefer_curly_fors:", "PreferCurlyFors" in registered)
print("configured_lowercase_token:", configured.group(0).strip() if configured else None)
print("exact_registered_match:", configured and configured.group(0).strip() in registered)
print("registered_rules:", registered)
PY

Repository: evolution-gaming/random

Length of output: 2764


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- build plugin configuration ---'
cat -n project/plugins.sbt
printf '%s\n' '--- Scalafmt configuration version declarations ---'
rg -n -i -uu 'scalafmt|version\s*=' .scalafmt.conf .github project build.sbt 2>/dev/null || true

printf '%s\n' '--- sbt-scalafmt 2.6.2 dependency metadata ---'
curl -fsSL \
  https://repo1.maven.org/maven2/org/scalameta/sbt-scalafmt_2.12_1.0/2.6.2/sbt-scalafmt_2.12_1.0-2.6.2.pom \
  | rg -n -C 2 'scalafmt|version' || true
curl -fsSL \
  https://repo1.maven.org/maven2/org/scalameta/sbt-scalafmt_2.12_1.0/2.6.2/sbt-scalafmt_2.12_1.0-2.6.2.pom \
  | head -40 || true

Repository: evolution-gaming/random

Length of output: 4965


🏁 Script executed:

#!/bin/bash
set -euo pipefail

for version in v3.11.4 v3.11.5; do
  printf '%s\n' "--- Rewrite.scala ${version} ---"
  curl -fsSL \
    "https://raw.githubusercontent.com/scalameta/scalafmt/${version}/scalafmt-core/shared/src/main/scala/org/scalafmt/rewrite/Rewrite.scala" \
    | sed -n '/private val rewrites/,/implicit val reader/p'
done

printf '%s\n' '--- Configured rewrite-rule tokens ---'
sed -n '/^rewrite\.rules[[:space:]]*=/,/^[[:space:]]*]/p' .scalafmt.conf

Repository: evolution-gaming/random

Length of output: 1106


Use the registered PreferCurlyFors rule name.

Line 85 uses prefercurlyfors, which Scalafmt does not recognize. This prevents the fmt and check tasks from decoding .scalafmt.conf.

Proposed fix
-  prefercurlyfors,
+  PreferCurlyFors,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
rewrite.rules = [
Imports,
RedundantParens,
SortModifiers,
prefercurlyfors,
]
rewrite.rules = [
Imports,
RedundantParens,
SortModifiers,
PreferCurlyFors,
]
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.scalafmt.conf around lines 81 - 86, Update the rewrite.rules entry in
.scalafmt.conf to use the registered PreferCurlyFors rule name instead of the
unrecognized lowercase prefercurlyfors value, preserving the other configured
rules.


# 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\\..*"],
]
}
27 changes: 9 additions & 18 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -34,7 +31,7 @@ libraryDependencies ++= Seq(
Cats.core,
CatsEffect.effect,
`cats-helper`,
scalatest % Test
scalatest % Test,
)

autoAPIMappings := true
Expand All @@ -46,27 +43,21 @@ 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(
Developer(
"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")
10 changes: 5 additions & 5 deletions project/Dependencies.scala
Original file line number Diff line number Diff line change
@@ -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
}
}
2 changes: 1 addition & 1 deletion project/build.properties
Original file line number Diff line number Diff line change
@@ -1 +1 @@
sbt.version=1.12.2
sbt.version = 1.12.15
8 changes: 3 additions & 5 deletions project/plugins.sbt
Original file line number Diff line number Diff line change
@@ -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")

Expand All @@ -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")
103 changes: 52 additions & 51 deletions src/main/scala-2/com/evolutiongaming/random/Random.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand All @@ -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)
}

Expand Down
Loading