March Docs

Random

Random module: purely-functional pseudorandom number generation.

Algorithm: sfc32 ("Small Fast Counting", from PractRand) on 32-bit words. State: 4 × 32-bit non-negative integers packed into the Rng record (s0/s1/s2 = sfc32 a/b/c, s3 = the counter). Seeding: splitmix32-style hash expansion of the seed's base-2^32 digits, followed by a 12-round warm-up.

Why 32-bit: every intermediate value stays far below 2^53, so sequences are exact — and IDENTICAL — on the interpreter, native, and JS backends. (The previous 63-bit xoshiro256 core needed exact 63-bit wraparound, which the JS target's double-backed Int cannot represent, and its use of int_shr on negative words silently diverged between the interpreter's logical shift and native's arithmetic shift.)

Usage: let rng = Random.seed(42) let (x, rng2) = Random.next_int(rng) let (f, rng3) = Random.next_float(rng2)

All functions are pure: they take an Rng and return (value, new_Rng). No global mutable state.

Types

typeRngRng = { s0 : Int, s1 : Int, s2 : Int, s3 : Int }#

Functions

fnbernoullibernoulli(rng : Rng, p : {Float | _ >= 0.0 && _ <= 1.0}) : (Bool, Rng)#

Sample from a Bernoulli distribution with success probability p. p must be in [0.0, 1.0]; panics otherwise.

fnchoicechoice(rng : Rng, xs : {List(a) | len(_) > 0}) : (a, Rng)#

Pick a uniform element from a non-empty list. Returns (element, new_rng). Panics on empty input.

fnchoice_weightedchoice_weighted(rng : Rng, items : List((a, Float))) : (a, Rng)#

Weighted random selection from a list of (item, weight) pairs.

Weights must be non-negative and at least one must be positive.
Returns `(chosen_item, new_rng)`.  Linear in the number of items.

Example:
  let items = [(:cat, 1.0), (:dog, 2.0), (:fish, 0.5)]
  let (pet, rng2) = Random.choice_weighted(rng, items)
  -- pet is one of :cat, :dog, :fish with probability ~0.28, 0.57, 0.14
fnexponentialexponential(rng : Rng, lambda : {Float | _ > 0.0}) : (Float, Rng)#

Sample from the exponential distribution with rate lambda > 0. Uses the inverse-CDF method: if U ~ Uniform(0, 1) then X = -ln(U) / lambda is Exp(lambda). Panics if lambda <= 0.

fnintint(lo : Int, hi : Int) : Int#

Generate a random integer in [lo, hi] (inclusive) using the current time as seed.

fnnext_floatnext_float(rng : Rng) : (Float, Rng)#

Generate a pseudorandom float in [0.0, 1.0).

fnnext_intnext_int(rng : Rng) : (Int, Rng)#

Generate the next pseudorandom non-negative integer, uniform in [0, 2^52). 52 bits is the widest range that stays exact on every backend (the JS target stores Int in an IEEE double).

fnnormalnormal(rng : Rng, mu : Float, sigma : {Float | _ >= 0.0}) : (Float, Rng)#

Sample from the normal distribution N(mu, sigma^2). Panics if sigma < 0. sigma=0 returns mu.

fnrangerange(rng : Rng, lo : Int, hi : Int) : (Int, Rng)#

Generate a random integer in [lo, hi] (inclusive). Spans up to 2^52.

fnsamplesample(rng : Rng, xs : List(a), n : Int) : (List(a), Rng)#

Sample n elements from xs without replacement. If n >= length(xs), returns all elements (shuffled). Returns (sample, new_rng).

fnseedseed(n : Int) : Rng#

Create a deterministic Rng from an integer seed.

fnshuffleshuffle(rng : Rng, xs : List(a)) : (List(a), Rng)#

Shuffle a list using the Fisher-Yates algorithm. Returns (shuffled_list, new_rng). O(n²) on lists. Same seed + same list always produces the same shuffle.

fnstandard_normalstandard_normal(rng : Rng) : (Float, Rng)#

Sample from the standard normal distribution N(0, 1) using the Box–Muller transform. Consumes two uniform samples per call and discards the second Gaussian deviate (simple + stateless; the pairing optimization requires a stateful RNG wrapper).