Random
Random is a pseudo-random number generator exposed through Effect’s context.
Instead of reaching for Math.random() directly, you read randomness from the
current Random service. That single indirection is what makes random programs
reproducible: in a test or a local simulation you can seed the generator and
get the exact same sequence every time.
Random is a Context.Reference, which means it is
always available — there is no layer to provide. A default Math.random-backed
implementation is installed for you, and you can override it (per-effect with
withSeed, or globally with a custom generator) wherever you need
determinism.
import { Effect, Random } from "effect"
const program = Effect.gen(function*() { const randomFloat = yield* Random.next // 0 <= x < 1 const randomInt = yield* Random.nextInt // any safe integer const diceRoll = yield* Random.nextIntBetween(1, 6)
return { randomFloat, randomInt, diceRoll }})All of the generators below return an Effect with no error channel
(Effect.Effect<A>) — drawing a random value cannot fail.
Generating values
Section titled “Generating values”The core generators cover floats, integers, booleans, and bounded ranges. Read
them with yield* inside Effect.gen:
import { Effect, Random } from "effect"
const program = Effect.gen(function*() { // A double in [0, 1) — the analogue of Math.random() const f = yield* Random.next
// A coin flip const flip = yield* Random.nextBoolean
// A safe integer anywhere in the full safe-integer range const big = yield* Random.nextInt
// A float in a custom range [0, 10) const temp = yield* Random.nextBetween(0, 10)
// An integer in a range — inclusive by default, so this is a d6 const dice = yield* Random.nextIntBetween(1, 6)
// Exclude the upper bound: a uniform index into a 6-element array const index = yield* Random.nextIntBetween(0, 6, { halfOpen: true })
return { f, flip, big, temp, dice, index }})Shuffling collections
Section titled “Shuffling collections”shuffle randomly reorders any iterable and returns a new Array (the input is
not mutated). It uses a Fisher–Yates shuffle driven by the active Random
service:
import { Effect, Random } from "effect"
const program = Effect.gen(function*() { const deck = yield* Random.shuffle([1, 2, 3, 4, 5]) console.log(deck) // => e.g. [3, 1, 5, 2, 4]
// Works on any iterable, e.g. a Set const picks = yield* Random.shuffle(new Set(["a", "b", "c"])) console.log(picks) // => e.g. ["b", "c", "a"]})Determinism and testing
Section titled “Determinism and testing”Because randomness flows through the service, you can replace it with a seeded
generator using withSeed. The same seed always produces the same
sequence, so a flaky “it depends on randomness” test becomes a deterministic
one:
import { Effect, Random } from "effect"
const program = Effect.gen(function*() { const a = yield* Random.next const b = yield* Random.next return [a, b] as const})
// Same seed => identical output, run after runconst seeded1 = program.pipe(Random.withSeed("test-seed"))const seeded2 = program.pipe(Random.withSeed("test-seed"))
// Both promises resolve to the same pair of numbersEffect.runPromise(seeded1)Effect.runPromise(seeded2)This is exactly the mechanism that underpins reproducible, property-based-style tests: seed the program, exercise it, and if it fails you can replay the same random sequence to debug it. See the testing guide for how to structure deterministic tests around services like this.
API reference
Section titled “API reference”Generates a random double in [0, 1) (inclusive of 0, exclusive of 1) — the
direct analogue of Math.random().
import { Effect, Random } from "effect"
Effect.gen(function*() { const x = yield* Random.next // => 0.5488135039273248 (0 <= x < 1)})nextBoolean
Section titled “nextBoolean”Generates a random boolean — true roughly half the time. (Internally it draws a
double and returns whether it is greater than 0.5.)
import { Effect, Random } from "effect"
Effect.gen(function*() { const b = yield* Random.nextBoolean // => true | false})nextInt
Section titled “nextInt”Generates a random safe integer across the entire range from
Number.MIN_SAFE_INTEGER to Number.MAX_SAFE_INTEGER (inclusive). Use
nextIntBetween when you want a bounded integer.
import { Effect, Random } from "effect"
Effect.gen(function*() { const n = yield* Random.nextInt // => -3920481039402011 (some safe integer)})nextBetween
Section titled “nextBetween”(min: number, max: number) => Effect<number> — generates a random float in
[min, max) (inclusive of min, exclusive of max).
import { Effect, Random } from "effect"
Effect.gen(function*() { const x = yield* Random.nextBetween(10, 20) // => 14.73... (10 <= x < 20)})nextIntBetween
Section titled “nextIntBetween”(min: number, max: number, options?: { halfOpen?: boolean }) => Effect<number> —
generates a random integer in a rounded range. The lower bound is rounded up
(Math.ceil), the upper bound rounded down (Math.floor). Inclusive of both
bounds by default; pass { halfOpen: true } to exclude the upper bound.
import { Effect, Random } from "effect"
Effect.gen(function*() { // Inclusive: result is one of 1, 2, 3, 4, 5, 6 const dice = yield* Random.nextIntBetween(1, 6) // => 4
// Half-open: result is one of 0, 1, 2, 3, 4, 5 (good for array indices) const idx = yield* Random.nextIntBetween(0, 6, { halfOpen: true }) // => 5})shuffle
Section titled “shuffle”<A>(elements: Iterable<A>) => Effect<Array<A>> — returns a new array with the
elements of any iterable in random order, leaving the input untouched.
import { Effect, Random } from "effect"
Effect.gen(function*() { const shuffled = yield* Random.shuffle(["a", "b", "c", "d"]) // => ["c", "a", "d", "b"]})withSeed
Section titled “withSeed”// data-last (for .pipe)withSeed(seed: string | number): <A, E, R>(self: Effect<A, E, R>) => Effect<A, E, R>// data-firstwithSeed<A, E, R>(self: Effect<A, E, R>, seed: string | number): Effect<A, E, R>Runs an effect with a deterministic generator seeded by the given string or
number. Internally it provides a seeded ISAAC-based generator in place of the
default Random service for the duration of self. The same seed always yields
the same sequence. It is dual, so it works both in .pipe(...) and as a direct
call.
import { Effect, Random } from "effect"
const draw = Random.next
// data-last, via pipeconst a = draw.pipe(Random.withSeed(42))
// data-first, direct callconst b = Random.withSeed(draw, 42)
// a and b resolve to the same numberEffect.runPromise(a) // => 0.123...Effect.runPromise(b) // => 0.123... (identical)Random (the reference)
Section titled “Random (the reference)”const Random: Context.Reference<{ nextIntUnsafe(): number nextDoubleUnsafe(): number}>The service itself. It is a Context.Reference holding a
minimal generator interface: nextDoubleUnsafe() returns a double in [0, 1) and
nextIntUnsafe() returns a safe integer. Every function above is derived from
these two methods. The Unsafe suffix means they are plain, synchronous,
non-Effect functions — prefer the wrapped generators (Random.next,
Random.nextInt, …) in application code.
You normally interact with it through withSeed, but you can also
provide a fully custom generator — for example a fixed sequence in a test, or a
secure source in production:
import { Effect, Random } from "effect"
// A custom generator that always returns the same values — handy for asserting// on code paths that branch on randomness.const fixed = { nextDoubleUnsafe: () => 0.5, nextIntUnsafe: () => 7}
const program = Effect.gen(function*() { const d = yield* Random.next // => 0.5 const n = yield* Random.nextInt // => 7 const r = yield* Random.nextIntBetween(1, 100) // derived from 0.5 => 51 return { d, n, r }})
// Override the reference for this effectconst deterministic = program.pipe( Effect.provideService(Random, fixed))
Effect.runPromise(deterministic) // => { d: 0.5, n: 7, r: 51 }