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
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