(std random)

Reference

Overview

Random numbers: ChaCha20, one generator per thread, and sources of your own.

(import (std random))

(random-int 1 6)                          ; the calling thread's generator
(random-int 1 6 #:rng my-source)          ; one of your own
(with-random-seed 42UL (deal-a-hand))     ; a run that replays

The generator is ChaCha20, seeded with 256 bits from the operating system’s CSPRNG, one per thread. The default path locks nothing and shares nothing, so drawing from two threads at once costs what drawing from one costs. A source you make yourself is locked instead, once per operation.

Every function takes #:rng, which defaults to (parameter-ref current-random). A program that wants another source binds the parameter and changes no call site.

The ambient source. The value of current-random holds no generator. It names the calling thread’s, and which thread that is is decided at each draw. That is what makes the default safe on a bjoroutine: a fiber may resume on a thread it did not suspend on, and a source read before the suspension would otherwise go on drawing from a generator another fiber is already inside. The ambient source has no state to race on.

A bound source is safe to share, and still does not replay. Every operation on a source you made takes its lock, so no draw is lost, doubled or torn however fibers interleave. What the lock cannot give is what the seed was for: the stream is one sequence, but which fiber gets which value of it is the scheduler’s choice.

(parameterize ((current-random (make-random/seed64 42UL)))
  (bjo (worker))     ; these two share one generator, under its lock:
  (bjo (worker)))    ; safe, but the order they draw in is not fixed

Give each fiber (random-split (parameter-ref current-random)), or a seed of its own, and the run replays. The default source needs neither: it is one generator per thread, reached by no other.

The lock is per operation, not per draw. shuffle-array! and random-bytes! are many draws and take the lock once, so a shuffle is the permutation the stream describes rather than one interleaved with another fiber’s. It also means that shuffling a large array under a source other fibers are drawing from makes them wait.

Binding a seeded source makes everything under it predictable, including code you did not have in mind: a session token minted inside a with-random-seed is guessable. Keep the extent to what is being replayed.

Seeded means reproducible, not unguessable. make-random/seed64 and make-random/seed256 take what they are given. Use make-random, or the default, for anything anyone else must not predict.

See also: random-int, current-random, make-random, with-random-seed

Reference

Types

Functions

Values

Macros

Types

RandomSource

type alias

Where random numbers come from: the ambient source, or a generator of its own.

A source made by make-random and its neighbours holds a generator, and every operation on it takes its lock. The ambient source, the default value of current-random, holds none and draws from the calling thread’s.

See also: current-random, make-random, random-split

Functions

make-random

function

(: make-random (-> RandomSource))

A new generator, seeded with 256 bits from the operating system.

returns
A source of its own: unpredictable, and locked per operation.

See also: make-random/seed64, random-split

make-random/seed64

function

(: make-random/seed64 (-> ulong RandomSource))

A generator that replays: the same seed draws the same numbers.

seed
Any 64 bits.
returns
A source of its own, locked per operation.

The 64 bits are stretched to ChaCha20’s 256-bit key by SplitMix64, so only 264 of the generators are reachable. Reproducible is not unguessable: use make-random for anything that must not be predicted.

See also: make-random/seed256, with-random-seed

make-random/seed256

function

(: make-random/seed256 (-> ulong ulong ulong ulong RandomSource))

A generator that replays, seeded at ChaCha20’s full key width.

a
The first 64 bits of the key.
b
The next 64.
c
The next 64.
d
The last 64. Four words make the key, so no seed is malformed.
returns
A source of its own, locked per operation.

See also: make-random/seed64

random-split

function

(: random-split (-> RandomSource RandomSource))

An independent generator, drawn from another source.

r
The source to draw the new generator's key from. On the ambient source, the calling thread's generator.
returns
A source of its own.
(def mine (random-split (parameter-ref current-random)))
(bjo (worker #:rng mine))

How a fiber gets a generator of its own: split one per fiber from a seeded source and the run replays however the fibers are scheduled.

See also: make-random, with-random-seed

random-int

function

(: random-int (-> int int (#:rng RandomSource) int))
(random-int lo hi #:rng (parameter-ref current-random))

A number from lo to hi, both included.

lo
The least it may answer.
hi
The most it may answer. Not less than lo.
#:rng
The source to draw from.
returns
A number in [lo, hi], each equally likely.
raises System.ArgumentException
When hi is less than lo.
(random-int 1 6)   ; a die

Lemire’s method, so there is no modulo bias. Mind that hi is included: (random-int 0 (vec-length v)) can be one past the end, and random-ref is the way to pick an element.

See also: random-ref, random-double

random-double

function

(: random-double (-> (#:rng RandomSource) double))
(random-double #:rng (parameter-ref current-random))

A number from 0, included, to 1, not included.

#:rng
The source to draw from.
returns
A double in [0, 1), with 53 bits of randomness.

See also: random-int, random-chance?

random-u32

function

(: random-u32 (-> (#:rng RandomSource) uint))
(random-u32 #:rng (parameter-ref current-random))

32 random bits.

#:rng
The source to draw from.
returns
A uint, every value equally likely.

See also: random-u64, random-bytes!

random-u64

function

(: random-u64 (-> (#:rng RandomSource) ulong))
(random-u64 #:rng (parameter-ref current-random))

64 random bits.

#:rng
The source to draw from.
returns
A ulong, every value equally likely.

See also: random-u32, random-bytes!

random-bool

function

(: random-bool (-> (#:rng RandomSource) bool))
(random-bool #:rng (parameter-ref current-random))

#t or #f, equally likely.

#:rng
The source to draw from.
returns
One random bit. They are drawn 64 at a time.

See also: random-chance?

random-chance?

function

(: random-chance? (-> double (#:rng RandomSource) bool))
(random-chance? p #:rng (parameter-ref current-random))

#t with probability p.

p
How likely #t is, from 0 to 1.
#:rng
The source to draw from.
returns
#t with probability p. Outside [0, 1] it is the nearer of #f and #t, and costs no draw.
(when (random-chance? 0.01) (log-sample request))

See also: random-bool, random-double

random-bytes!

function

(: random-bytes! (-> (Array byte) (#:rng RandomSource) void))
(random-bytes! bytes #:rng (parameter-ref current-random))

Fills an array with random bytes, in place.

bytes
The array to fill.
#:rng
The source to draw from.

Whole blocks of the generator’s output go straight into the array, so this is the way to ask for many bytes at once. A source of its own is locked once for the whole fill.

See also: random-u64

shuffle-array!

function

(: shuffle-array! (-> (Array %a) (#:rng RandomSource) void))
(shuffle-array! a #:rng (parameter-ref current-random))

Shuffles an array in place.

a
The array, which is shuffled.
#:rng
The source to draw from.

Fisher-Yates. It answers nothing: the array it was given is the shuffled one. A source of its own is locked once for the whole shuffle.

See also: shuffle-vec

shuffle-vec

function

(: shuffle-vec (-> (Vec %a) (#:rng RandomSource) (Vec %a)))
(shuffle-vec v #:rng (parameter-ref current-random))

A shuffled copy of a vec.

v
The vec, which is left as it was.
#:rng
The source to draw from.
returns
A new vec with v's elements in random order.

A Vec is persistent, so unlike shuffle-array! this answers a new one.

See also: shuffle-array!

random-ref

function

(: random-ref (-> (Vec %a) (#:rng RandomSource) %a))
(random-ref v #:rng (parameter-ref current-random))

An element of a vec, each equally likely.

v
The vec to pick from. Not empty.
#:rng
The source to draw from.
returns
One of v's elements.
raises ArgumentException
When v is empty.
(random-ref ["rock" "paper" "scissors"])

Only for a Vec: picking from any collection would need Refable’s index pinned to int, which a where clause cannot say.

See also: random-int, shuffle-vec

random-string

function

(: random-string (-> int (#:rng RandomSource) string))
(random-string length #:rng (parameter-ref current-random))

A random string over A-Z, a-z, 0-9, - and _.

length
How many characters.
#:rng
The source to draw from.
returns
A string of that many characters, each of the 64 equally likely.
raises System.ArgumentOutOfRangeException
When length is negative.

Six bits per character, which suits a token in a URL. Draw it from the default source or from make-random, never under a seed, when it must not be guessed.

See also: random-string/chars, random-bytes!

random-string/chars

function

(: random-string/chars (-> int (Vec char) (#:rng RandomSource) string))
(random-string/chars length alphabet #:rng (parameter-ref current-random))

A random string over an alphabet of your own.

length
How many characters.
alphabet
The characters to pick from, each equally likely. Not empty.
#:rng
The source to draw from.
returns
A string of that many characters from alphabet.
raises ArgumentException
When alphabet is empty, or length is negative.
(random-string/chars 6 (string->vec "0123456789"))   ; a PIN

See also: random-string

Values

current-random

value

(: current-random (Param RandomSource))

The source every function draws from when it is not given #:rng.

(parameterize ((current-random (make-random/seed64 7UL)))
  (random-int 1 6))   ; the same number every run

A (Param RandomSource). Its default value is the ambient source, which draws from the calling thread’s generator at each draw, so it is safe to read on a bjoroutine and to hand to another thread.

See also: with-random-seed, RandomSource

Macros

with-random-seed

macro

(with-random-seed seed body ...)

Runs body with a generator that replays.

seed
A ulong; the same seed draws the same numbers.
body ...
What to run with it.
(with-random-seed 42UL
  (deal-a-hand))   ; the same hand every run

current-random bound to (make-random/seed64 seed) for body’s extent. That is one generator for the whole extent: a fiber spawned in it shares it, safely, but draws in the scheduler’s order, so the run stops replaying. A fiber wants random-split or a seed of its own.

See also: current-random, make-random/seed64, random-split