Repository navigation
Random
Status: first slice landed. compute-engine's own Random draws from one seeded stream per
engine (@enumeratio/engine's random.ts) over finite collections, intervals and
distributions, with a count or shape; RandomInteger and RandomVariate rewrite to it;
Permutations(n) is a collection it can sample. Still their own heads: RandomComplex,
RandomGraph and RandomFunction, which build a random object with no domain to draw from yet
(below), and compute-engine's RandomChoice, RandomSample, RandomPrime, which are not ours
to fold.
Wolfram has a head per thing to draw -- RandomInteger, RandomReal, RandomComplex,
RandomChoice, RandomSample, RandomVariate, RandomPrime, RandomGraph, … -- because
its type system can't say "a random element of this". Ours can. What varies is not the verb
but the domain, and the domain already knows how to be sampled:
-
A finite collection samples itself. Anything with a count and an unrank draws an index
uniformly and unranks it. That is exact, needs no registration, and already works:
compute-engine's
Randomtakes acollection, andRandom(Subsets(5)),Random(DyckPaths(4)),Random(Range(1, 10))answer today, typed as the element. -
Everything else says how. A distribution samples by its own method; an unbounded family
needs a size (or a distribution over sizes); a graph family draws by its model. Each
implements a sampling protocol for its type (
declareProtocolImplementation), soRandomnever grows an arm per domain.
((indexed_collection<T>) random -> T)
& ((set<real>) random -> real) -- compute-engine's intervals
& ((T) random -> element<T> where T is Sampleable) -- the protocol
with a count argument for draws with or without replacement (RandomChoice/RandomSample
are Random with a count and a replacement option). A refinement of a sampleable type is
sampleable: a restriction samples by rejection unless it brings a sampler of its own (worth
it when the restriction is thin). One head carries the random effect; SeedRandom and
WithRandomSeed stay as they are.
RandomInteger(n) is Random(Range(0, n)), RandomReal(x) is Random(Interval(0, x)),
RandomVariate(dist) is Random(dist), RandomChoice(list, k) and RandomSample(list, k)
are Random(list, k) with and without replacement, RandomPrime(n) is
Random(Primes ∩ Range(2, n)), RandomGraph(n, m) is Random(Graphs(n, m)). Each is a
data alias that rewrites to Random, the way the numerals' old names do, so Epsil and the
Wolfram transpiler and oracle keep their spelling.
A type's protocol conformances go on its record, beside its overloads: "can this be
sampled, counted, ranked?" is then answerable from the manifest without loading code, and
the same record field serves Count, Rank, Unrank and Length, which have the same
shape (one verb, the capability on the type).
-
One stream.
seedRandom/uniform01in@enumeratio/engine:SeedRandom(n)restarts it, andRandom,RandomInteger,RandomVariate,RandomGraph,RandomComplexand the process paths all draw from it -- twomulberry32streams (collections', statistics') were one too many.RandomInteger(a, b)andRandom(Range(a, b))are the same draw. -
Samplers. A finite collection is sampled by
countandat, uniformly; past the safe-integer range the engine has no count andRandomdeclines rather than draw unevenly (bigint indices are a follow-up). A package registers a sampler for its own domain (registerSampler) and adds its overload (addRandomArm): statistics does distributions. That registry is the sampling protocol in all but name; making it a compute-engine protocol, recorded on each type's record, is the next step, and the same shape servesCount,RankandUnrank. -
Permutations(n)was missing: compute-engine'sPermutationstakes a collection, so an integer was a type error. It is now one more arm,(integer<0..>) -> indexed_collection, meaningSymmetricGroup(n).
RandomGraph(n, m) becomes Random(Graphs(n, m)) once a graph family is a collection (or a
sampled model); RandomComplex becomes Random over a complex region once there is one;
RandomFunction over a process is Random of a path.