1/**
2 * Provides pseudo-random generation through an Effect service.
3 *
4 * This module exposes effectful generators for booleans, doubles, safe
5 * integers, bounded numbers, shuffling, and deterministic seeded runs. Because
6 * random generation is a service, tests and applications can replace the
7 * generator used by Effect programs.
8 *
9 * @since 4.0.0
10 */
11import type * as Arr from "./Array.ts"
12import * as Cause from "./Cause.ts"
13import type * as Context from "./Context.ts"
14import * as Effect from "./Effect.ts"
15import { dual } from "./Function.ts"
16import * as random from "./internal/random.ts"
17import type * as NonEmptyIterable from "./NonEmptyIterable.ts"
18import * as Predicate from "./Predicate.ts"
19
20/**
21 * The service used to generate pseudo-random numbers.
22 *
23 * @category services
24 * @since 4.0.0
25 */
26export interface Random {
27 /**
28 * Generates a random safe integer.
29 */
30 nextIntUnsafe(): number
31 /**
32 * Generates a random number between 0 (inclusive) and 1 (exclusive).
33 */
34 nextDoubleUnsafe(): number
35}
36
37/**
38 * Represents a service for generating pseudo-random numbers.
39 *
40 * **When to use**
41 *
42 * Use to access or provide the random-number generator service used by Effect
43 * programs.
44 *
45 * **Gotchas**
46 *
47 * The default implementation is based on `Math.random` and is not
48 * cryptographically secure. Replace the service with a cryptographically secure
49 * implementation before using these generators for security-sensitive values.
50 *
51 * **Example** (Accessing the random service)
52 *
53 * ```ts import.meta.vitest
54 * import { Effect, Random } from "effect"
55 *
56 * const program = Effect.gen(function*() {
57 * const float = yield* Random.next
58 * const integer = yield* Random.nextInt
59 * const inRange = yield* Random.nextIntBetween(1, 100)
60 * return [float, integer, inRange] as const
61 * })
62 *
63 * await Effect.runPromise(program.pipe(Random.withSeed("example"))) // => [0.1633802591287037, 3434461687501127, 1]
64 * ```
65 *
66 * @category services
67 * @since 2.0.0
68 */
69export const Random: Context.Reference<Random> = random.Random
70
71const randomWith = <A>(f: (random: Random) => A): Effect.Effect<A> =>
72 Effect.withFiber((fiber) => Effect.succeed(f(fiber.getRef(Random))))
73
74/**
75 * Generates a random number between 0 (inclusive) and 1 (exclusive).
76 *
77 * **When to use**
78 *
79 * Use to generate a pseudo-random floating-point number in the standard
80 * `[0, 1)` range.
81 *
82 * **Example** (Generating a random number)
83 *
84 * ```ts import.meta.vitest
85 * import { Effect, Random } from "effect"
86 *
87 * await Effect.runPromise(Random.next.pipe(Random.withSeed("example"))) // => 0.1633802591287037
88 * ```
89 *
90 * @category generators
91 * @since 2.0.0
92 */
93export const next: Effect.Effect<number> = randomWith((r) => r.nextDoubleUnsafe())
94
95/**
96 * Generates a random boolean value.
97 *
98 * **When to use**
99 *
100 * Use to make a pseudo-random true-or-false choice.
101 *
102 * **Example** (Generating a random boolean)
103 *
104 * ```ts import.meta.vitest
105 * import { Effect, Random } from "effect"
106 *
107 * await Effect.runPromise(Random.nextBoolean.pipe(Random.withSeed("example"))) // => false
108 * ```
109 *
110 * @category generators
111 * @since 2.0.0
112 */
113export const nextBoolean: Effect.Effect<boolean> = randomWith((r) => r.nextDoubleUnsafe() > 0.5)
114
115/**
116 * Generates a random integer between `Number.MIN_SAFE_INTEGER` (inclusive)
117 * and `Number.MAX_SAFE_INTEGER` (inclusive).
118 *
119 * **When to use**
120 *
121 * Use to generate a pseudo-random safe integer across the full safe-integer
122 * range.
123 *
124 * **Example** (Generating a random integer)
125 *
126 * ```ts import.meta.vitest
127 * import { Effect, Random } from "effect"
128 *
129 * await Effect.runPromise(Random.nextInt.pipe(Random.withSeed("example"))) // => -6064002158214091
130 * ```
131 *
132 * @category generators
133 * @since 2.0.0
134 */
135export const nextInt: Effect.Effect<number> = randomWith((r) => r.nextIntUnsafe())
136
137/**
138 * Generates a random number between `min` (inclusive) and `max` (exclusive).
139 *
140 * **When to use**
141 *
142 * Use to generate a pseudo-random floating-point number within a numeric range.
143 *
144 * **Example** (Generating a bounded random number)
145 *
146 * ```ts import.meta.vitest
147 * import { Effect, Random } from "effect"
148 *
149 * await Effect.runPromise(Random.nextBetween(0, 1).pipe(Random.withSeed("example"))) // => 0.1633802591287037
150 * ```
151 *
152 * @category generators
153 * @since 4.0.0
154 */
155export const nextBetween = (min: number, max: number): Effect.Effect<number> =>
156 randomWith((r) => random.nextBetween(min, max, r.nextDoubleUnsafe()))
157
158/**
159 * Generates a random integer between `min` and `max`.
160 *
161 * **When to use**
162 *
163 * Use to generate a pseudo-random integer within a rounded numeric range.
164 *
165 * **Details**
166 *
167 * The lower bound is rounded up with `Math.ceil` and the upper bound is
168 * rounded down with `Math.floor`. By default the range is inclusive; set
169 * `options.halfOpen: true` to exclude the upper bound.
170 *
171 * **Example** (Generating a bounded random integer)
172 *
173 * ```ts import.meta.vitest
174 * import { Effect, Random } from "effect"
175 *
176 * const program = Effect.gen(function*() {
177 * const diceRoll1 = yield* Random.nextIntBetween(1, 6)
178 * const diceRoll2 = yield* Random.nextIntBetween(1, 6, {
179 * halfOpen: true
180 * })
181 * const diceRoll3 = yield* Random.nextIntBetween(0, 10)
182 * return [diceRoll1, diceRoll2, diceRoll3]
183 * })
184 *
185 * await Effect.runPromise(program.pipe(Random.withSeed("example"))) // => [1, 4, 0]
186 * ```
187 *
188 * @category generators
189 * @since 2.0.0
190 */
191export const nextIntBetween = (min: number, max: number, options?: {
192 readonly halfOpen?: boolean
193}): Effect.Effect<number> => {
194 const extra = options?.halfOpen === true ? 0 : 1
195 return randomWith((r) => {
196 const minInt = Math.ceil(min)
197 const maxInt = Math.floor(max)
198 return Math.floor(r.nextDoubleUnsafe() * (maxInt - minInt + extra)) + minInt
199 })
200}
201
202/**
203 * Uses the pseudo-random number generator to shuffle the specified iterable.
204 *
205 * **When to use**
206 *
207 * Use to randomly reorder an iterable using the active `Random` service.
208 *
209 * **Example** (Shuffling values)
210 *
211 * ```ts import.meta.vitest
212 * import { Effect, Random } from "effect"
213 *
214 * await Effect.runPromise(Random.shuffle([1, 2, 3, 4, 5]).pipe(Random.withSeed("example"))) // => [4, 2, 5, 3, 1]
215 * ```
216 *
217 * @category generators
218 * @since 2.0.0
219 */
220export const shuffle = <A>(elements: Iterable<A>): Effect.Effect<Array<A>> =>
221 randomWith((r) => {
222 const buffer = Array.from(elements)
223 for (let i = buffer.length - 1; i >= 1; i = i - 1) {
224 const index = Math.min(i, Math.floor(r.nextDoubleUnsafe() * (i + 1)))
225 const value = buffer[i]!
226 buffer[i] = buffer[index]!
227 buffer[index] = value
228 }
229 return buffer
230 })
231
232/**
233 * Gets a random element from an iterable.
234 *
235 * **When to use**
236 *
237 * Use to select one value uniformly from a collection using the active `Random`
238 * service.
239 *
240 * **Details**
241 *
242 * If the input type is known to be non-empty, the returned effect cannot fail.
243 * Otherwise, empty iterables fail with `Cause.NoSuchElementError`.
244 *
245 * **Example** (Choosing a random value)
246 *
247 * ```ts import.meta.vitest
248 * import { Effect, Random } from "effect"
249 *
250 * await Effect.runPromise(Random.choice(["red", "green", "blue"] as const).pipe(Random.withSeed("example"))) // => "red"
251 * ```
252 *
253 * @category generators
254 * @since 3.6.0
255 */
256export const choice: <Self extends Iterable<unknown>>(
257 elements: Self
258) => Self extends NonEmptyIterable.NonEmptyIterable<infer A> ? Effect.Effect<A>
259 : Self extends Arr.NonEmptyReadonlyArray<infer A> ? Effect.Effect<A>
260 : Self extends Iterable<infer A> ? Effect.Effect<A, Cause.NoSuchElementError>
261 : never = ((elements: Iterable<unknown>) => {
262 const buffer = Array.from(elements)
263 return buffer.length === 0
264 ? Effect.fail(new Cause.NoSuchElementError("Cannot select a random element from an empty array"))
265 : randomWith((r) => buffer[Math.min(buffer.length - 1, Math.floor(r.nextDoubleUnsafe() * buffer.length))]!)
266 }) as any
267
268/**
269 * Seeds the pseudo-random number generator with the specified value.
270 *
271 * **When to use**
272 *
273 * Use to run an effect with a deterministic pseudo-random sequence.
274 *
275 * **Details**
276 *
277 * Using the same seed produces the same random sequence, which is useful for
278 * tests and reproducible simulations.
279 *
280 * **Gotchas**
281 *
282 * Use an unpredictable seed when uniqueness or unpredictability matters.
283 *
284 * **Example** (Seeding random generation)
285 *
286 * ```ts import.meta.vitest
287 * import { Effect, Random } from "effect"
288 *
289 * const program = Effect.gen(function*() {
290 * const value1 = yield* Random.next
291 * const value2 = yield* Random.next
292 * return [value1, value2]
293 * })
294 *
295 * await Effect.runPromise(Effect.all([
296 * program.pipe(Random.withSeed("my-seed")),
297 * program.pipe(Random.withSeed("my-seed"))
298 * ])) // => [[0.018368576514773527, 0.4010840628128671], [0.018368576514773527, 0.4010840628128671]]
299 * ```
300 *
301 * @category providing services
302 * @since 4.0.0
303 */
304export const withSeed: {
305 (seed: string | number): <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>
306 <A, E, R>(self: Effect.Effect<A, E, R>, seed: string | number): Effect.Effect<A, E, R>
307} = dual(2, <A, E, R>(
308 self: Effect.Effect<A, E, R>,
309 seed: string | number
310) => Effect.provideService(self, Random, ISAAC_CSPRNG(seed)))
311
312/*///////////////////////////////////////////////////////////////////////////////////////////////////
313This is a derivative work copyright (c) 2025 Effectful Technologies Inc, under MIT license.
314This is a derivative work copyright (c) 2018, William P. "Mac" McMeans, under BSD license.
315Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
3161. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
3172. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
3183. Neither the name of isaacCSPRNG nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
319THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
320Original work copyright (c) 2012 Yves-Marie K. Rinquin, under MIT license.
321https://github.com/rubycon/isaac.js
322///////////////////////////////////////////////////////////////////////////////////////////////////*/
323function ISAAC_CSPRNG(userSeed?: string | number) {
324 // Internal State
325 const memory = new Array(256)
326 const result = new Array(256)
327 let accumulator = 0
328 let lastResult = 0
329 let generation = 0
330 let counter = 0
331
332 // Initial Seed
333 const internalSeed = Predicate.isUndefined(userSeed) ? getInitialSeed() : userSeed
334 seed(internalSeed)
335
336 function getInitialSeed() {
337 const uint32a = new Uint32Array(2)
338 crypto.getRandomValues(uint32a)
339 return uint32a[0] + uint32a[1]
340 }
341
342 function reset() {
343 accumulator = 0
344 lastResult = 0
345 counter = 0
346 for (let i = 0; i < 256; ++i) {
347 memory[i] = 0
348 result[i] = 0
349 }
350 generation = 0
351 }
352
353 function seed(userSeed: string | number): void {
354 // The golden ratio ( 2654435769 )
355 // See https://stackoverflow.com/questions/4948780/magic-number-in-boosthash-combine
356 const magicNumber = 0x9e3779b9
357 let a = magicNumber
358 let b = magicNumber
359 let c = magicNumber
360 let d = magicNumber
361 let e = magicNumber
362 let f = magicNumber
363 let g = magicNumber
364 let h = magicNumber
365 let i = 0
366
367 let seed: Array<number>
368 if (Predicate.isString(userSeed)) {
369 seed = toIntArray(userSeed)
370 } else {
371 seed = [userSeed]
372 }
373
374 reset()
375
376 for (i = 0; i < seed.length; i++) {
377 result[i & 0xff] += seed[i]
378 }
379
380 function mix() {
381 a ^= b << 11
382 d = add32(d, a)
383 b = add32(b, c)
384
385 b ^= c >>> 2
386 e = add32(e, b)
387 c = add32(c, d)
388
389 c ^= d << 8
390 f = add32(f, c)
391 d = add32(d, e)
392
393 d ^= e >>> 16
394 g = add32(g, d)
395 e = add32(e, f)
396
397 e ^= f << 10
398 h = add32(h, e)
399 f = add32(f, g)
400
401 f ^= g >>> 4
402 a = add32(a, f)
403 g = add32(g, h)
404
405 g ^= h << 8
406 b = add32(b, g)
407 h = add32(h, a)
408
409 h ^= a >>> 9
410 c = add32(c, h)
411 a = add32(a, b)
412 }
413
414 // Scramble the seed
415 for (i = 0; i < 4; i++) {
416 mix()
417 }
418
419 for (i = 0; i < 256; i += 8) {
420 // Use all the information in the seed
421 a = add32(a, result[i])
422 b = add32(b, result[i + 1])
423 c = add32(c, result[i + 2])
424 d = add32(d, result[i + 3])
425 e = add32(e, result[i + 4])
426 f = add32(f, result[i + 5])
427 g = add32(g, result[i + 6])
428 h = add32(h, result[i + 7])
429
430 mix()
431
432 // Fill in the memory with messy stuff
433 memory[i] = a
434 memory[i + 1] = b
435 memory[i + 2] = c
436 memory[i + 3] = d
437 memory[i + 4] = e
438 memory[i + 5] = f
439 memory[i + 6] = g
440 memory[i + 7] = h
441 }
442
443 // Second pass to make sure seed affects memory
444 for (i = 0; i < 256; i += 8) {
445 a = add32(a, memory[i])
446 b = add32(b, memory[i + 1])
447 c = add32(c, memory[i + 2])
448 d = add32(d, memory[i + 3])
449 e = add32(e, memory[i + 4])
450 f = add32(f, memory[i + 5])
451 g = add32(g, memory[i + 6])
452 h = add32(h, memory[i + 7])
453
454 mix()
455
456 // Fill in the memory with messy stuff (again)
457 memory[i] = a
458 memory[i + 1] = b
459 memory[i + 2] = c
460 memory[i + 3] = d
461 memory[i + 4] = e
462 memory[i + 5] = f
463 memory[i + 6] = g
464 memory[i + 7] = h
465 }
466
467 pnrg()
468
469 generation = 256
470 }
471
472 function pnrg(n?: number): void {
473 let i = 0
474 let x = 0
475 let y = 0
476
477 n = Predicate.isUndefined(n) ? 1 : Math.abs(Math.floor(n))
478
479 while (n--) {
480 counter = add32(counter, 1)
481 lastResult = add32(lastResult, counter)
482
483 for (i = 0; i < 256; i++) {
484 switch (i & 3) {
485 case 0: {
486 accumulator ^= accumulator << 13
487 break
488 }
489 case 1: {
490 accumulator ^= accumulator >>> 6
491 break
492 }
493 case 2: {
494 accumulator ^= accumulator << 2
495 break
496 }
497 case 3: {
498 accumulator ^= accumulator >>> 16
499 break
500 }
501 }
502
503 accumulator = add32(memory[(i + 128) & 0xff], accumulator)
504 x = memory[i]
505
506 memory[i] = add32(memory[(x >>> 2) & 0xff], add32(accumulator, lastResult))
507 y = memory[i]
508
509 result[i] = add32(memory[(y >>> 10) & 0xff], x)
510 lastResult = result[i]
511 }
512 }
513 }
514
515 /**
516 * Returns a signed, random integer in the range [-2^31, 2^31).
517 */
518 function nextInt32(): number {
519 if (!generation--) {
520 pnrg()
521 generation = 255
522 }
523 return result[generation]
524 }
525
526 function nextIntUnsafe(): number {
527 return Math.floor(nextDoubleUnsafe() * (Number.MAX_SAFE_INTEGER - Number.MIN_SAFE_INTEGER + 1)) +
528 Number.MIN_SAFE_INTEGER
529 }
530
531 /**
532 * Returns a 53-bit fraction in the range [0, 1).
533 */
534 function nextDoubleUnsafe(): number {
535 const hi = (nextInt32() >>> 0) & 0x1FFFFF // take top 21 bits
536 const lo = nextInt32() >>> 0 // full 32 bits
537
538 // 53-bit integer
539 const combined = hi * 4294967296 + lo
540
541 return combined / 0x20000000000000 // [0, 1)
542 }
543
544 return { nextIntUnsafe, nextDoubleUnsafe }
545}
546
547/**
548 * 32-bit addition with overflow handling (JavaScript numbers are 53-bit).
549 *
550 * Example: add32(0xFFFFFFFF, 0x00000001) = 0x00000000 (wraps around)
551 */
552function add32(x: number, y: number): number {
553 // Add lower 16 bits separately to handle carry
554 // Example: x=0x12345678, y=0xABCDEF01
555 // lsb = (0x5678 + 0xEF01) = 0x14579
556 const lsb = (x & 0xffff) + (y & 0xffff)
557
558 // Add upper 16 bits + carry from lower addition
559 // msb = (0x1234 + 0xABCD + (0x14579 >>> 16)) = (0x1234 + 0xABCD + 0x1) = 0xBE02
560 const msb = (x >>> 16) + (y >>> 16) + (lsb >>> 16)
561
562 // Combine: upper 16 bits | lower 16 bits (masked to prevent double carry)
563 // return (0xBE02 << 16) | (0x14579 & 0xffff) = 0xBE024579
564 return (msb << 16) | (lsb & 0xffff)
565}
566
567const seedEncoder = new TextEncoder()
568
569/**
570 * Convert a string to UTF-8 encoded 32-bit integers (little-endian).
571 */
572function toIntArray(seed: string): Array<number> {
573 const bytes = seedEncoder.encode(seed)
574 const result: Array<number> = []
575
576 for (let index = 0; index < bytes.length; index += 4) {
577 result.push(
578 ((bytes[index] ?? 0) << 0) |
579 ((bytes[index + 1] ?? 0) << 8) |
580 ((bytes[index + 2] ?? 0) << 16) |
581 ((bytes[index + 3] ?? 0) << 24)
582 )
583 }
584
585 return result
586}