import * as Context from "../../Context.js" import { pipe } from "../../Function.js" import * as Layer from "../../Layer.js" import type * as STM from "../../STM.js" import type * as TArray from "../../TArray.js" import type * as TRandom from "../../TRandom.js" import type * as TRef from "../../TRef.js" import * as Random from "../../Utils.js" import * as core from "./core.js" import * as stm from "./stm.js" import * as tArray from "./tArray.js" import * as tRef from "./tRef.js" const TRandomSymbolKey = "effect/TRandom" /** @internal */ export const TRandomTypeId: TRandom.TRandomTypeId = Symbol.for( TRandomSymbolKey ) as TRandom.TRandomTypeId const randomInteger = (state: Random.PCGRandomState): [number, Random.PCGRandomState] => { const prng = new Random.PCGRandom() prng.setState(state) return [prng.integer(0), prng.getState()] } const randomIntegerBetween = (low: number, high: number) => { return (state: Random.PCGRandomState): [number, Random.PCGRandomState] => { const prng = new Random.PCGRandom() prng.setState(state) return [prng.integer(high - low) + low, prng.getState()] } } const randomNumber = (state: Random.PCGRandomState): [number, Random.PCGRandomState] => { const prng = new Random.PCGRandom() prng.setState(state) return [prng.number(), prng.getState()] } const withState = ( state: TRef.TRef, f: (state: Random.PCGRandomState) => [A, Random.PCGRandomState] ): STM.STM => { return pipe(state, tRef.modify(f)) } const shuffleWith = ( iterable: Iterable, nextIntBounded: (n: number) => STM.STM ): STM.STM> => { const swap = (buffer: TArray.TArray, index1: number, index2: number): STM.STM => pipe( buffer, tArray.get(index1), core.flatMap((tmp) => pipe( buffer, tArray.updateSTM(index1, () => pipe(buffer, tArray.get(index2))), core.zipRight( pipe( buffer, tArray.update(index2, () => tmp) ) ) ) ) ) return pipe( tArray.fromIterable(iterable), core.flatMap((buffer) => { const array: Array = [] for (let i = array.length; i >= 2; i = i - 1) { array.push(i) } return pipe( array, stm.forEach((n) => pipe(nextIntBounded(n), core.flatMap((k) => swap(buffer, n - 1, k))), { discard: true }), core.zipRight(tArray.toArray(buffer)) ) }) ) } /** @internal */ export const Tag = Context.GenericTag("effect/TRandom") class TRandomImpl implements TRandom.TRandom { readonly [TRandomTypeId]: TRandom.TRandomTypeId = TRandomTypeId constructor(readonly state: TRef.TRef) { this.next = withState(this.state, randomNumber) this.nextBoolean = core.flatMap(this.next, (n) => core.succeed(n > 0.5)) this.nextInt = withState(this.state, randomInteger) } next: STM.STM nextBoolean: STM.STM nextInt: STM.STM nextRange(min: number, max: number): STM.STM { return core.flatMap(this.next, (n) => core.succeed((max - min) * n + min)) } nextIntBetween(low: number, high: number): STM.STM { return withState(this.state, randomIntegerBetween(low, high)) } shuffle(elements: Iterable): STM.STM> { return shuffleWith(elements, (n) => this.nextIntBetween(0, n)) } } /** @internal */ export const live: Layer.Layer = Layer.effect( Tag, pipe( tRef.make(new Random.PCGRandom((Math.random() * 4294967296) >>> 0).getState()), core.map((seed) => new TRandomImpl(seed)), core.commit ) ) /** @internal */ export const next: STM.STM = core.flatMap(Tag, (random) => random.next) /** @internal */ export const nextBoolean: STM.STM = core.flatMap(Tag, (random) => random.nextBoolean) /** @internal */ export const nextInt: STM.STM = core.flatMap(Tag, (random) => random.nextInt) /** @internal */ export const nextIntBetween = (low: number, high: number): STM.STM => core.flatMap(Tag, (random) => random.nextIntBetween(low, high)) /** @internal */ export const nextRange = (min: number, max: number): STM.STM => core.flatMap(Tag, (random) => random.nextRange(min, max)) /** @internal */ export const shuffle = (elements: Iterable): STM.STM, never, TRandom.TRandom> => core.flatMap(Tag, (random) => random.shuffle(elements))