8. Random Functions

The random/ library makes reproducible random values from a seed. It reads no host entropy: random/seeded always takes an explicit seed, and the same seed gives the same values on every host and in every later version.

Table of Contents

8.1. Generators ↑

A generator is a mutable value that the runtime owns. Each call that takes a generator advances it in place, as regex/find advances a matcher. Two calls on the same generator give the next two values of one sequence:

(let [g (random/seeded 42)]
  [(random/int .g 0 6) (random/int .g 0 6)])
; [0 2] on every run

The algorithm is xoshiro256**, seeded through SplitMix64: the generator's four 64-bit state words are the first four SplitMix64 outputs for the seed. This choice is fixed.

A generator in a result is written as its algorithm and its current state, 64 lowercase hexadecimal digits for the four state words in order, each most significant digit first:

#rng {xoshiro256ss "9d1c68b67d1ceb4323acb714c3a78d019d431c524cad8dc572e82a070b0b9ddb"}

The state written is the one the generator has when the result is exported, after the whole evaluation, not when the generator is placed in the result. So [.g (random/int .g 0 6)] shows in its first slot the state after the random/int call.

A #rng value read back from a document, or bound from the host, becomes a new generator that continues the same sequence. It changes into a generator in place when a random function first uses it, so every reference to it shares one state. A payload that is not one xoshiro256ss member with 64 lowercase hexadecimal digits, or whose state is all zero, produces #err <invalid tag> when a random function uses it. Two generators are equal when their states are equal.

DUST
(let [g (random/seeded 42)]
  [(random/int .g 0 6) (random/int .g 0 6) .g])
→
Canonical DUST
(let [g (random/seeded 42)] [(random/int .g 0 6) (random/int .g 0 6) .g])
=
DUST Result
0
2
#rng {xoshiro256ss 9d1c68b67d1ceb4323acb714c3a78d019d431c524cad8dc572e82a070b0b9ddb}
DUST
(let [g #rng {xoshiro256ss "9d1c68b67d1ceb4323acb714c3a78d019d431c524cad8dc572e82a070b0b9ddb"}]
  [(random/int .g 0 6) (random/int .g 0 6) (random/int .g 0 6) .g])
→
Canonical DUST
(let
  [g #rng {xoshiro256ss 9d1c68b67d1ceb4323acb714c3a78d019d431c524cad8dc572e82a070b0b9ddb}]
  [(random/int .g 0 6) (random/int .g 0 6) (random/int .g 0 6) .g])
=
DUST Result
4
5
5
#rng {xoshiro256ss 7e3fedbea92a13a5c9a25ba0f11c828cc38346747039f414cf55c271f2386fa5}
DUST
(random/int #rng {xoshiro256ss "00"} 0 6)
(random/int #rng {xoshiro256ss "0000000000000000000000000000000000000000000000000000000000000000"} 0 6)
(random/int #rng {xoshiro256ss "6DB07C7DD404690B81DDC5FE6C15769825CFE223490D9D1F9252543D113B0C36"} 0 6)
(random/int #rng {pcg32 "6db07c7dd404690b81ddc5fe6c15769825cfe223490d9d1f9252543d113b0c36"} 0 6)
(random/int #rng 42 0 6)
(random/int #rng {xoshiro256ss "bdd732262feb6e9528efe333b266f10347526757130f9f52581ce1ff0e4ae394" extra true} 0 6)
→
Canonical DUST
(random/int #rng {xoshiro256ss 00} 0 6)
(random/int
  #rng {xoshiro256ss 0000000000000000000000000000000000000000000000000000000000000000}
  0
  6)
(random/int
  #rng {xoshiro256ss 6DB07C7DD404690B81DDC5FE6C15769825CFE223490D9D1F9252543D113B0C36}
  0
  6)
(random/int #rng {pcg32 6db07c7dd404690b81ddc5fe6c15769825cfe223490d9d1f9252543d113b0c36} 0 6)
(random/int #rng 42 0 6)
(random/int
  #rng {<extra>
        xoshiro256ss bdd732262feb6e9528efe333b266f10347526757130f9f52581ce1ff0e4ae394}
  0
  6)
=
DUST Result
#err {<invalid tag>
      message "#rng requires a xoshiro256ss state of 64 hex digits, not all zero"}
#err {<invalid tag>
      message "#rng requires a xoshiro256ss state of 64 hex digits, not all zero"}
#err {<invalid tag>
      message "#rng requires a xoshiro256ss state of 64 hex digits, not all zero"}
#err {<invalid tag>
      message "#rng requires a xoshiro256ss state of 64 hex digits, not all zero"}
#err {<invalid tag>
      message "#rng requires a xoshiro256ss state of 64 hex digits, not all zero"}
#err {<invalid tag>
      message "#rng requires a xoshiro256ss state of 64 hex digits, not all zero"}

Ranges are half-open: lo is included and hi is not. An empty range, where lo > hi=, produces #err <invalid value>. A bound of the wrong type, or a value that is not a generator, produces #err <mismatch type>.

8.2. random/seeded ↑

Function

(random/seeded seed) → generator

Returns a new generator for the integer seed. Every integer is a seed, negative ones included.

Call Result
(random/seeded 42) #rng {xoshiro256ss bdd732262feb6e9528efe333b266f10347526757130f9f52581ce1ff0e4ae394}
(random/seeded 0) #rng {xoshiro256ss e220a8397b1dcdaf6e789e6aa1b965f406c45d188009454ff88bb8a8724c81ec}
(random/seeded 1.5) #err <mismatch type>
DUST
(random/seeded 42)
(random/seeded 0)
(random/seeded -1)
(random/seeded -9223372036854775808)
=
DUST Result
#rng {xoshiro256ss bdd732262feb6e9528efe333b266f10347526757130f9f52581ce1ff0e4ae394}
#rng {xoshiro256ss e220a8397b1dcdaf6e789e6aa1b965f406c45d188009454ff88bb8a8724c81ec}
#rng {xoshiro256ss e4d971771b652c20e99ff867dbf682c9382ff84cb27281e96d1db36ccba982d2}
#rng {xoshiro256ss 481ec0a212a9f3dbc46fa638a630901261a685ffc80a8140592e268383e356f9}
DUST
(let [a (random/seeded 7)
      b (random/seeded 7)]
  [(random/int .a 0 100) (random/int .b 0 100) (random/int .a 0 100) (random/int .b 0 100)])
→
Canonical DUST
(let
  [a (random/seeded 7) b (random/seeded 7)]
  [(random/int .a 0 100) (random/int .b 0 100) (random/int .a 0 100) (random/int .b 0 100)])
=
DUST Result
70
70
27
27
DUST
(random/seeded 1.5)
(random/seeded abc)
(random/seeded)
(random/int 42 0 6)
(random/int #other {xoshiro256ss "6db07c7dd404690b81ddc5fe6c15769825cfe223490d9d1f9252543d113b0c36"} 0 6)
→
Canonical DUST
(random/seeded 1.5)
(random/seeded abc)
(random/seeded)
(random/int 42 0 6)
(random/int
  #other {xoshiro256ss 6db07c7dd404690b81ddc5fe6c15769825cfe223490d9d1f9252543d113b0c36}
  0
  6)
=
DUST Result
#err {<mismatch type>
      message "random/seeded requires an integer seed"}
#err {<mismatch type>
      message "random/seeded requires an integer seed"}
#err {<arity mismatch>
      message "wrong number of arguments"}
#err {<mismatch type>
      message "expected a random generator"}
#err {<mismatch type>
      message "expected a random generator"}

8.3. random/int ↑

Function

(random/int generator lo hi) → integer

Returns an integer x with lo < x < hi=, uniform over the range. The range can span all 64-bit integers. Integers use Lemire's multiply-and-reject method, so no value is more likely than another.

Call, on (random/seeded 42) Result
(random/int .g 0 6) 0
(random/int .g -10 -5) -10
(random/int .g -9223372036854775808 9223372036854775807) -7676373272452217067
(random/int .g 5 5) #err <invalid value>
DUST
(let [g (random/seeded 42)]
  [(random/int .g 0 6) (random/int .g 0 6) (random/int .g 0 6) (random/int .g 0 6) (random/int .g 0 6)
   (random/int .g 0 6) (random/int .g 0 6) (random/int .g 0 6) (random/int .g 0 6) (random/int .g 0 6)])
(let [g (random/seeded 42)]
  [(random/int .g -10 -5) (random/int .g 5 6) (random/int .g -1000000000000000000 1000000000000000000)])
(let [g (random/seeded 7)]
  [(random/int .g 0 3) (random/int .g 0 3) (random/int .g 0 3) (random/int .g 0 3) (random/int .g 0 3)])
→
Canonical DUST
(let
  [g (random/seeded 42)]
  [(random/int .g 0 6) (random/int .g 0 6) (random/int .g 0 6) (random/int .g 0 6)
   (random/int .g 0 6) (random/int .g 0 6) (random/int .g 0 6)
   (random/int .g 0 6) (random/int .g 0 6) (random/int .g 0 6)])
(let
  [g (random/seeded 42)]
  [(random/int .g -10 -5) (random/int .g 5 6)
   (random/int .g -1000000000000000000 1000000000000000000)])
(let
  [g (random/seeded 7)]
  [(random/int .g 0 3) (random/int .g 0 3) (random/int .g 0 3)
   (random/int .g 0 3) (random/int .g 0 3)])
=
DUST Result
[[0 2 4 5 5 4 4 5 4 3] [-10 5 360086822056278777] [2 0 2 2 2]]
DUST
(let [g (random/seeded 42)]
  [(random/int .g -9223372036854775808 9223372036854775807)
   (random/int .g -9223372036854775808 9223372036854775807)
   (random/int .g -9223372036854775808 9223372036854775807)])
(let [g (random/seeded 42)]
  [(random/int .g -1000000000000000000 1000000000000000000)
   (random/int .g -1000000000000000000 1000000000000000000)
   (random/int .g -1000000000000000000 1000000000000000000)])
→
Canonical DUST
(let
  [g (random/seeded 42)]
  [(random/int .g -9223372036854775808 9223372036854775807)
   (random/int .g -9223372036854775808 9223372036854775807)
   (random/int .g -9223372036854775808 9223372036854775807)])
(let
  [g (random/seeded 42)]
  [(random/int .g -1000000000000000000 1000000000000000000)
   (random/int .g -1000000000000000000 1000000000000000000)
   (random/int .g -1000000000000000000 1000000000000000000)])
=
DUST Result
[[-7676373272452217067 -2232420343890232707 3321214725393783200]
 [-832274057880235478 -242039498674662721 360086822056278777]]
DUST
(let [g #rng {xoshiro256ss "0000000000000001000000000000000000000000000000010000000000000001"}]
  [(random/int .g 0 6) .g])
(let [g #rng {xoshiro256ss "0000000000000001000000000000000000000000000000010000000000000001"} warm (random/seeded 7)]
  (for [i (lists/range 48)] (random/int .warm 0 2))
  [(random/int .g 0 6) .g])
→
Canonical DUST
(let
  [g #rng {xoshiro256ss 0000000000000001000000000000000000000000000000010000000000000001}]
  [(random/int .g 0 6) .g])
(let
  [g
   #rng {xoshiro256ss 0000000000000001000000000000000000000000000000010000000000000001}
   warm
   (random/seeded 7)]
  (for [i (lists/range 48)] (random/int .warm 0 2))
  [(random/int .g 0 6) .g])
=
DUST Result
[[0 #rng {xoshiro256ss 0000000004000080000020000400000040000000040000000010000004000000}]
 [0 #rng {xoshiro256ss 0000000004000080000020000400000040000000040000000010000004000000}]]
DUST
(random/int (random/seeded 1) 5 5)
(random/int (random/seeded 1) 6 0)
(random/int (random/seeded 1) 9223372036854775807 -9223372036854775808)
=
DUST Result
#err {<invalid value>
      message "random range is empty: lo must be below hi"}
#err {<invalid value>
      message "random range is empty: lo must be below hi"}
#err {<invalid value>
      message "random range is empty: lo must be below hi"}
DUST
(random/int (random/seeded 1) 0.0 6.0)
(random/int (random/seeded 1) 0 6.5)
(random/int (random/seeded 1) a z)
(random/int (random/seeded 1) 6)
=
DUST Result
#err {<mismatch type>
      message "random/int requires integer bounds"}
#err {<mismatch type>
      message "random/int requires integer bounds"}
#err {<mismatch type>
      message "random/int requires integer bounds"}
#err {<arity mismatch>
      message "wrong number of arguments"}

8.4. random/float ↑

Function

(random/float generator lo hi) → float

Returns a float x with lo < x < hi=. Both bounds are floats. The function takes the top 53 bits of the next output as u in [0, 1), and maps it to lo + u * (hi - lo). When hi - lo is too large for a float, it uses lo * (1 - u) + hi * u. A result that rounds up to hi is drawn again.

Call, on (random/seeded 42) Result
(random/float .g 0.0 1.0) 0.08386297105988216
(random/float .g -2.5 2.5) -2.080685144700589
(random/float .g 0 1) #err <mismatch type>
DUST
(let [g (random/seeded 42)]
  [(random/float .g 0.0 1.0) (random/float .g 0.0 1.0) (random/float .g 0.0 1.0)])
(let [g (random/seeded 42)]
  [(random/float .g -2.5 2.5) (random/float .g 100.0 100.5) (random/float .g -1.0e308 1.0e308)])
(let [g (random/seeded 7)]
  [(random/float .g 0.0 1.0e-300) (random/float .g -1.0 0.0)])
→
Canonical DUST
(let
  [g (random/seeded 42)]
  [(random/float .g 0.0 1.0) (random/float .g 0.0 1.0) (random/float .g 0.0 1.0)])
(let
  [g (random/seeded 42)]
  [(random/float .g -2.5 2.5) (random/float .g 100.0 100.5) (random/float .g -1e+308 1e+308)])
(let [g (random/seeded 7)] [(random/float .g 0.0 1e-300) (random/float .g -1.0 0.0)])
=
DUST Result
[[0.08386297105988216 0.3789802506626686 0.6800434110281394]
 [-2.080685144700589 100.18949012533133 3.600868220562787e+307]
 [7.0057648217968965e-301 -0.7212487705262157]]
DUST
(random/float (random/seeded 1) 1.0 1.0)
(random/float (random/seeded 1) 2.0 1.0)
=
DUST Result
#err {<invalid value>
      message "random range is empty: lo must be below hi"}
#err {<invalid value>
      message "random range is empty: lo must be below hi"}
DUST
(random/float (random/seeded 1) 0 1)
(random/float (random/seeded 1) 0.0 1)
(random/float (random/seeded 1) a b)
=
DUST Result
#err {<mismatch type>
      message "random/float requires float bounds"}
#err {<mismatch type>
      message "random/float requires float bounds"}
#err {<mismatch type>
      message "random/float requires float bounds"}

8.5. random/choose ↑

Function

(random/choose generator list) → value

Returns one item of list, each with the same chance. An empty list or null produces #err <invalid value>.

Call, on (random/seeded 42) Result
(random/choose .g [a b c]) a
(random/choose .g [only]) only
(random/choose .g []) #err <invalid value>
DUST
(let [g (random/seeded 42)]
  [(random/choose .g [a b c]) (random/choose .g [a b c]) (random/choose .g [a b c]) (random/choose .g [a b c])])
(let [g (random/seeded 42)]
  [(random/choose .g [only]) (random/choose .g [1 2.5 "three" {four 4}])])
(let [g (random/seeded 7)]
  (random/choose .g [[1 2] [3 4]]))
→
Canonical DUST
(let
  [g (random/seeded 42)]
  [(random/choose .g [a b c]) (random/choose .g [a b c])
   (random/choose .g [a b c]) (random/choose .g [a b c])])
(let [g (random/seeded 42)] [(random/choose .g [only]) (random/choose .g [1 2.5 three {four 4}])])
(let [g (random/seeded 7)] (random/choose .g [[1 2] [3 4]]))
=
DUST Result
[[a b c c] [only 2.5] [3 4]]
DUST
(random/choose (random/seeded 1) [])
(random/choose (random/seeded 1) null)
=
DUST Result
#err {<invalid value>
      message "random/choose requires a nonempty list"}
#err {<invalid value>
      message "random/choose requires a nonempty list"}
DUST
(random/choose (random/seeded 1) {a 1})
(random/choose (random/seeded 1) abc)
(random/choose [a b])
=
DUST Result
#err {<mismatch type>
      message "expected a list or null"}
#err {<mismatch type>
      message "expected a list or null"}
#err {<arity mismatch>
      message "wrong number of arguments"}

8.6. random/shuffle ↑

Function

(random/shuffle generator list) → list

Returns a new list with the items of list in a uniform random order, and leaves list unchanged. The order comes from a Fisher–Yates shuffle that swaps each position i, from the last down to the second, with a position from 0 to i. null shuffles to [].

Call, on (random/seeded 7) Result
(random/shuffle .g [1 2 3 4 5]) [1 5 3 2 4]
(random/shuffle .g []) []
(random/shuffle .g {a 1}) #err <mismatch type>
DUST
(let [g (random/seeded 7)]
  [(random/shuffle .g [1 2 3 4 5]) (random/shuffle .g [1 2 3 4 5]) (random/shuffle .g [a b c d e f g h])])
(let [g (random/seeded 7)
      xs [1 2 3]]
  [(random/shuffle .g .xs) .xs])
(let [g (random/seeded 7)]
  [(random/shuffle .g []) (random/shuffle .g [only]) (random/shuffle .g null)])
→
Canonical DUST
(let
  [g (random/seeded 7)]
  [(random/shuffle .g [1 2 3 4 5]) (random/shuffle .g [1 2 3 4 5])
   (random/shuffle .g [a b c d e f g h])])
(let [g (random/seeded 7) xs [1 2 3]] [(random/shuffle .g .xs) .xs])
(let
  [g (random/seeded 7)]
  [(random/shuffle .g []) (random/shuffle .g [only]) (random/shuffle .g null)])
=
DUST Result
[[[1 5 3 2 4] [2 3 1 4 5] [g a c e f h b d]] [[2 1 3] [1 2 3]] [[] [only] []]]
DUST
(random/shuffle (random/seeded 1) {a 1})
(random/shuffle (random/seeded 1) abc)
(random/shuffle [1 2 3])
=
DUST Result
#err {<mismatch type>
      message "expected a list or null"}
#err {<mismatch type>
      message "expected a list or null"}
#err {<arity mismatch>
      message "wrong number of arguments"}