The Machines · Statistics & machine learning

random

Seedless Randomness: machines/random.shoddy

the random machine's icon

Summary

random gives you random numbers, plainly. Three number words: Random for a fraction between 0 and 1, RandomRange for a floating-point number (one that can carry a fractional part) between two bounds, and RandomInt for a whole number between two bounds. Plus two list words: Shuffle for a random ordering, and Sample for draws that never pick the same item twice. All of them sit directly on the Rnd builtin (a word the engine itself supplies), which draws evenly-spread values from the engine's own generator. The machine is seedless by construction: there is no seed (a starting value that fixes the sequence) to create, store, or thread through your code. You call a word, you get a fresh random number. That is the whole thing, and the simplicity is the point.

A Brief History of the State of Sin

Deterministic machines (computers that, given the same start, always do exactly the same thing) were asked for random numbers almost as soon as they existed. The first big customers ran on chance: the Monte Carlo simulations of the atomic-bomb laboratories, which answer hard questions by running thousands of random trials. John von Neumann proposed the first widely used generator in the late 1940s, the middle-square method: square a number, take its middle digits, repeat. He knew exactly how disreputable the enterprise was. He said so in the line every textbook has quoted since: anyone who considers arithmetical methods of producing random digits is, of course, in a state of sin. The sinning continued productively. Derrick Lehmer's linear congruential generator of 1949 powered decades of language runtimes. Its modern successors are faster, and their period (how far the sequence runs before it repeats) is much longer. What never changed is the theology. A generated sequence is not random, only unpredictable enough for the purpose: good for shuffles, samples and simulations, never for secrets. This machine keeps to the safe side of that line. Its seedless design leaves the state where it belongs, in the engine.

Why It's Useful

Randomness is how a program stops being predictable. A dice roll, a shuffled deck, a coin flip, a wandering game character, a scattering of stars, a sampled guess: all of them need a source of numbers that is not the same every time. random is that source, with the two shapes you almost always want: a number somewhere in a range, or a whole number somewhere in a range. Because it is seedless, there is nothing to set up and nothing to carry around. Reach for it whenever you want a surprise and do not need to reproduce it exactly later. (When you do need reproducibility, the trade-off is spelled out below.)

User's Guide

Include the file and call the words; there is no state to manage. The one thing worth understanding is what makes this machine unusual, so here it is plainly: Random() is deliberately not a pure function. Everywhere else, Shoddy makes a firm promise: call a function twice with the same arguments and you get the same answer, every time, no exceptions. That predictability is the property the whole language is built to guarantee. Random is the one considered exception. Call it twice with no arguments and it can, and should, give you two different numbers. It is an effect, living at the edge of your program in exactly the same family as Print: something that reaches out to the world (here, the engine's running generator) rather than a closed calculation. Treat it that way. Keep it near the edges of your code, the way you would keep printing there, and let the pure parts stay pure.

Include "random.shoddy"

Def Main()
    Print(Random())                 ' e.g. 0.417... — a fraction in [0, 1)
    Print(RandomRange(1, 6))        ' e.g. 4.82...  — a float in [1, 6)
    Print(RandomInt(1, 6))          ' e.g. 5        — a die roll, 1 to 6

    ' Called twice, RandomInt gives two (probably) different answers —
    ' that impurity is the whole point.
    Print(RandomInt(1, 100))        ' e.g. 73
    Print(RandomInt(1, 100))        ' e.g. 12

    Let deck = Shuffle(Range(1, 10))    ' e.g. [ 7 2 9 ... ] — all ten, reordered
    Let hand = Sample(deck, 3)          ' e.g. [ 4 9 1 ]     — three, no repeats
    Print(hand)

A few things worth remembering:

Builtins

The four words the runtime dispatches: not defined here, documented here

These are not Defs. The engine dispatches them, and a Def whose name is a builtin is refused. They are documented on this page because random is the machine built directly on Rnd, and Shuffle, Sample and DropAt were its own words until lists became cons cells. They need no Include. The same entries are in machines/random.shoddy's own header block.

WordDescription
Rnd()A uniform random number in [0, 1): zero possible, one never. Impure, in the same way Print is: calling it twice gives two different answers, and it is the single reason nothing in this machine is a pure function. It draws from the engine's own auto-seeded generator. That is what "seedless by construction" means here: there is no generator state for a program to hold.
Shuffle(xs)A new list with the items of xs in uniformly random order. The original is untouched, and every ordering is equally likely. The draws are the ones the old Def made, so a seeded run answers the permutation it always did.
Sample(xs, n)A new list of n items drawn from xs without replacement: no item is picked twice. Ask for more than the list holds and you get all of it, shuffled; ask for none and you get an empty list.
DropAt(xs, k)xs without its k-th item; stops the program outside 1..Length. Pure, and here because the two above were written over it.

Seed is not here, and its absence is the point. Seed(n) reseeds this same generator, so it does govern every word below. But this machine's whole design is that there is no seed to create, so it is the wrong place to document the word that creates one. Seed lives on math's page among the other arithmetic primitives. Calling it there makes Random, RandomRange, RandomInt, Shuffle and Sample reproducible from that point on.

Word Reference

Every word, built on the Rnd builtin

Random numbers effectful; not pure

WordDescription
Random()A random floating-point number in [0, 1): 0 possible, 1 never. The Rnd builtin, given a plain name. Not a pure function: two calls can give two different answers.
RandomRange(lo, hi)A random floating-point number in [lo, hi). Stops the program with an error if hi is below lo.
RandomInt(lo, hi)A random whole number in [lo, hi], both ends included: a fair die between the two bounds. Stops the program with an error if hi is below lo.

Who Uses It

UserHow
neuralShuffle deals the mini-batches; RandomRange scatters the initial weights.

The Machines It Uses

None. A thin layer over the Rnd builtin, nothing else required.