What is the state of random generators?
I’ve started working on a library inspired by the rand
crate. It’s monadic (chainable via andThen
so the state is hidden), generic (provides abstractions via abilities (and I must say abilities in roc just awesome! Very smooth and intuitive experience!) so it’s possible to implement an arbitrary prng). It can also generate a list of bytes which might be useful in combination with the decoding ability. It’s only standard distribution for now, but yeah, it can be configured.
So, I’m planning to prepare a minimal poc. But maybe there are already developments on the same concept? I saw roc-random
and elm/random
but they’re not quite what I want.
We have https://github.com/lukewilliamboswell/roc-random
Sounds good :smiley: I'm not sure how much time Jan has had recently. It would be good to have another random library
I'm tracking anyone else working on random things
I also have made some fuzzer and adjacent random library stuff
But yeah, I think it would be easy to take some things like random. Make an ability for the prng (so the algorithm can be swapped out). Use monads and generators to get nice chaining and be able to generate any arbitrary type that implements an ability.
I think anyone can make a library like this without too much effort. Though without automatic derivation, and random generate isn't quite as awesome for arbitrary types.
So lots of options for what can be done and I think good apis shouldn't be too hard to build
The main disadvantage of any pure random solution like this is that it won't compare with tasks.
So a lot of platforms may want to implement random generations as tasks for composibility reasons...though maybe there is a smart solution here.
regarding tasks, I think #API Design > Random number generation via Task is a nice design once module params land! :smiley:
Just checking I understand it. Seed with a Task. Then for any use of it even though it is technically pure and can't fail, call Task.ok generateValue
such that the random generator makes tasks and is composable.
yeah basically you have something platforms can opt into supporting, which is "I, as the platform, will track the current seed in state that lives in the platform, and whenever you run one of these random-number-generating Task
s, I'll give you a new seed by stepping my old one and then continuing to persist it"
so it would be an effect; the stored seed in the platform would change
Ah yeah, I guess it wouldn't just seemless change with a random library cause the randomly library not only needs to return wrap the output in tasks, but every intermediate prng state much be gotten via tasks for it to be seamless
I wrote a super quick draft of a random generator (based on xorshift) with an iterator sort of interface.
One consideration I had in mind is seeding: building repeatable code (easily) is really useful for debugging randomised algorithms (for example monte carlo calculations or machine-learning training runs). A feature I haven't seen but would love to is quick efficient "branching" of random generators. That is, if I'm writing a function that receives a random generator as an argument and calls three different functions that each need a random generator, I want to spawn a new random generator off the current one and pass eac function a separate generator. That way each gets its own repeatble sequence, and changes to one function can't affect the sequence the others receive.
This does require randomness properties that are not usually tested for, but if it were a usable way of writing code I could imagine coming up with adequate generators.
If I understand you correctly, then that's exactly what I’m working on. For my purpose, I need exactly controllable prngs and not a single one somewhere on the platform side.
Yeah, it’s important to have an entropy source provided by the platform. But for me, it sounds like a special case of the high-level api for managing tasks, at the abstract level randomness is just a stream of bits. Also, sometimes it’s enough to just retrieve the seed from the platform to pass it then as an initialization for a pure prng
The interface I was aiming for only makes sense if we have iterators/lazy lists (which I'm trying to write), so you could write something like:
\generator ->
{before: [a, b], after: rest} = Iterator.split generator 2
(f unc1 (Random.fromSeed a)) + (func2 (Random.fromSeed b)) + (func3 rest)
but the point is, func1
and func2
do all their work on fresh random streams. I don't know how often one will want to successively pull values from the generator within one function, but I suppose a good interface for doing that for random numbers will possibly also work for more general streams of values. (The exception is that other, non-random, streams can run out.)
Unlike more side-effect-friendly languages, truly random numbers need to have a different interface in roc from pseudo-random numbers. Which is maybe actually good, since people who need truly random numbers generally need them for cryptography, where they wouldn't want to slip up and accidentally use pseudo-random numbers?
For my Monte Carlo stuff I picture (if not debugging) using a Task to request enough randomness from the platform to construct a pseudo-random generator and then pulling everything from the generator. If debugging. I'd want to pull the seed from the source code or from a config file or something.
The exception to this approach is arguably the most common case: if I don't actually care whether my numbers are truly random but I don't want to be passing a generator object everywhere. This is the common situation, for example with python's random.random()
or C's rand()
. In other languages one can even fudge the debugging situation by seeding the global random number generator with a deterministic value, but this is fraught with challenges - any reordering or skipping or additional work risks getting the global generator out of sync and scrambling your numbers. I think the approach roc could take here would be to suggest platforms provide a Task that returns true random numbers, a Task that seeds a global pseudo-random number generator (might as well be the platform's native one I guess), and a Task that returns values from the global generator. It does force the user to use Tasks every time they want randomness, but that's required by roc's purity.
I'm a bit confused by your use of true random numbers. Most people don't have hardware to generate true random numbers. Also, I would assume most platform random number generators will just be calling simple prngs.
I would expect some platforms to use cryptographically secure prngs, but that still isn't true random number generation.
Brendan Hansknecht said:
I'm a bit confused by your use of true random numbers. Most people don't have hardware to generate true random numbers. Also, I would assume most platform random number generators will just be calling simple prngs.
I would expect some platforms to use cryptographically secure prngs, but that still isn't true random number generation.
Linux provdes /dev/random
, which is supposed to gather entropy from unpredictable physical processes the OS has access to - precise timings of mouse movements, keystrokes, disk access times, network packets, ... - and produce genuinely random numbers. Many Intel machines actually have hardware designed to generate large volumes of genuinely random bits, but there is a certain amount of distrust in the cryptographic community for these. In any case I really do mean true randomness here.
Cryptographically secure PRNGs need not be platform-dependent; I would be very happy to see stream ciphers and the like available to all roc programs.
Most standard-library pseudo-random number generators make use of the small amount of randomness available from reading the system clock on startup to ensure that they don't produce the same stream every time. Here is an example suggesting people do this: https://farside.ph.utexas.edu/teaching/329/lectures/node25.html
I guess when I hear "true" random number generator, I think of the custom hardware for random number generation. Generally they are based on quantum mechanics nowadays. /dev/random
is reasonable especially on user machines and probably good enough in general, but it is not based on a truly random device. Especially if you are thinking of an isolated server without input (though in practice, that is a rare case, you aways at least have network).
Also, the totally entropy in /dev/random
can be used up technically or hit other issues, but yeah probably totally good enough for 100% of use cases.
Just to be clear, /dev/random
is labeled as: cryptographically secure pseudorandom number generators
(it just attempts to understand when entropy is low and block waiting on more entropy)
Found this curious:
Unless you are doing long-term key generation (and most likely
not even then), you probably shouldn't be reading from the
/dev/random device or employing getrandom(2) with the GRND_RANDOM
flag.
Linux docs really don't suggest the use of /dev/random
: https://man7.org/linux/man-pages/man7/random.7.html
Anyway, that was all a side tagent. Yeah, cryptographically secure prng that is continuously seeded with assumed to be random entropy from various hardware interactions/environment. As such, it is a perfectly good source of random data.
Most standard-library pseudo-random number generators make use of the small amount of randomness available from reading the system clock on startup to ensure that they don't produce the same stream every time.
Yeah, for most use cases, very simple prng + a simple seed on time is plenty random enough. Nowadays, pcg or xoroshiro for really fast implementation.
Last updated: Jul 05 2025 at 12:14 UTC