Random
This API helps you generate pseudo-random values.
It is an implementation of Permuted Congruential Generators by M. E. O'Neil. It is not cryptographically secure.
A Generator
is a recipe for generating random values. For example,
here is a generator for numbers between 1 and 10 inclusive:
import Random
oneToTen : Random.Generator Int
oneToTen =
Random.int 1 10
Notice that we are not actually generating any numbers yet! We are describing
what kind of values we want. To actually get random values, you create a
command with the generate
function:
type Msg = NewNumber Int
newNumber : Cmd Msg
newNumber =
Random.generate NewNumber oneToTen
Each time you run this command, it runs the oneToTen
generator and produces
random integers between one and ten.
Note 1: The random Generator
API is quite similar to the JSON Decoder
API, which we've inherited from Elm.
Both are building blocks that snap together with map
, map2
, etc. You can read
more about JSON decoders here to see the similarity.
Create a command that produces random values. Say you want to generate random points:
import Random
point : Random.Generator { first : Int, second : Int }
point =
Random.pair (Random.int -100 100) (Random.int -100 100)
type Msg = NewPoint { first : Int, second : Int }
newPoint : Cmd Msg
newPoint =
Random.generate NewPoint point
Each time you run the newPoint
command, it will produce a new 2D point like
{ first = 57, second = 18 }
or { first = -82, second = 6 }
.
Note: Read through guide.elm-lang.org to learn how commands work.
If you are coming from JS it can be hopelessly frustrating if you just try to
wing it. And definitely ask around on Slack if you feel stuck! Investing in
understanding generators is really worth it, and once it clicks, folks often
dread going back to Math.random()
in JavaScript.
Primitives
Generate 32-bit integers in a given range.
import Random
singleDigit : Random.Generator Int
singleDigit =
Random.int 0 9
closeToZero : Random.Generator Int
closeToZero =
Random.int -5 5
anyInt : Random.Generator Int
anyInt =
Random.int Random.minInt Random.maxInt
This generator can produce values outside of the range [minInt
,
maxInt
] but sufficient randomness is not guaranteed.
Generate floats in a given range.
import Random
probability : Random.Generator Float
probability =
Random.float 0 1
The probability
generator will produce values between zero and one with
a uniform distribution. Say it produces a value p
. We can then check if
p < 0.4
if we want something to happen 40% of the time.
This becomes very powerful when paired with functions like map
and
andThen
. Rather than dealing with twenty random float messages
in your update
, you can build up sophisticated logic in the Generator
itself!
Generate values with equal probability. Say we want a random suit for some cards:
import Random
type Suit = Diamond | Club | Heart | Spade
suit : Random.Generator Suit
suit =
Random.uniform Diamond [ Club, Heart, Spade ]
That generator produces all Suit
values with equal probability, 25% each.
Note: Why not have uniform : Array a -> Generator a
as the API? It looks
a little prettier in code, but it leads to an awkward question. What do you do
with uniform []
? How can it produce an Int
or Float
? The current API
guarantees that we always have at least one value, so we never run into that
question!
Generate values with a weighted probability. Say we want to simulate a loaded die that lands on ⚄ and ⚅ more often than the other faces:
import Random
type Face = One | Two | Three | Four | Five | Six
roll : Random.Generator Face
roll =
Random.weighted
{ weight : 10, value : One }
[ { weight : 10, value : Two }
, { weight : 10, value : Three }
, { weight : 10, value : Four }
, { weight : 20, value : Five }
, { weight : 40, value : Six }
]
So there is a 40% chance of getting Six
, a 20% chance of getting Five
, and
then a 10% chance for each of the remaining faces.
Note: I made the weights add up to 100, but that is not necessary. I always
add up your weights into a total
, and from there, the probablity of each case
is weight / total
. Negative weights do not really make sense, so I just flip
them to be positive.
Generate the same value every time.
import Random
alwaysFour : Random.Generator Int
alwaysFour =
Random.constant 4
Think of it as picking from a hat with only one thing in it. It is weird,
but it can be useful with elm-community/random-extra
which has
tons of nice helpers.
Data Structures
Generate a pair of random values. A common use of this might be to generate a point in a certain 2D space:
import Random
randomPoint : Random.Generator { first : Float, second : Float }
randomPoint =
Random.pair (Random.float -200 200) (Random.float -100 100)
Maybe you are doing an animation with SVG and want to randomly generate some entities?
Generate an array of random values.
import Random
tenFractions : Random.Generator (Array Float)
tenFractions =
Random.array 10 (Random.float 0 1)
fiveGrades : Random.Generator (Array Int)
fiveGrades =
Random.array 5 (int 0 100)
If you want to generate an array with a random length, you need to use
andThen
like this:
fiveToTenDigits : Random.Generator (Array Int)
fiveToTenDigits =
Random.int 5 10
|> Random.andThen (\len -> Random.array len (Random.int 0 9))
This generator gets a random integer between five and ten and then uses that to generate a random array of digits.
Mapping
Transform the values produced by a generator. For example, we can generate random boolean values:
import Random
bool : Random.Generator Bool
bool =
Random.map (\n -> n < 20) (Random.int 1 100)
The bool
generator first picks a number between 1 and 100. From there
it checks if the number is less than twenty. So the resulting Bool
will
be True
about 20% of the time.
You could also do this for lower case ASCII letters:
letter : Random.Generator Char
letter =
Random.map (\n -> Char.fromCode (n + 97)) (Random.int 0 25)
The letter
generator first picks a number between 0 and 25 inclusive.
It then uses Char.fromCode
to turn ASCII codes into Char
values.
Note: Instead of making these yourself, always check if the
random-extra
package has what you need!
Combine two generators. Maybe we have a space invaders game and want to generate enemy ships with a random location:
import Random
type alias Enemy =
{ health : Float
, rotation : Float
, x : Float
, y : Float
}
enemy : Random.Generator Enemy
enemy =
Random.map2
(\x y ->
{ health = 100
, rotation = 0
, x = x
, y = y
}
)
(Random.float 0 100)
(Random.float 0 100)
Now whenever we run the enemy
generator we get an enemy with full health,
no rotation, and a random position! Now say we want to start with between
five and ten enemies on screen:
initialEnemies : Random.Generator (Array Enemy)
initialEnemies =
Random.int 5 10
|> Random.andThen (\num -> Random.array num enemy)
We will generate a number between five and ten, and then generate that number of enemies!
Note: Snapping generators together like this is very important! Always
start by with generators for each type
you need, and then snap them
together.
Combine three generators. Maybe you want to make a simple slot machine?
import Random
type alias Spin =
{ one : Symbol
, two : Symbol
, three : Symbol
}
type Symbol = Cherry | Seven | Bar | Grapes
spin : Random.Generator Spin
spin =
Random.map3 (\one two three -> { one = one, two = two, three = three })
symbol
symbol
symbol
symbol : Random.Generator Symbol
symbol =
Random.uniform Cherry [ Seven, Bar, Grapes ]
Note: Always start with the types. Make a generator for each thing you need
and then put them all together with one of these map
functions.
Combine four generators.
Say you are making game and want to place enemies or terrain randomly. You could generate a quadtree!
import Random
type QuadTree a
= Empty
| Leaf a
| Node (QuadTree a) (QuadTree a) (QuadTree a) (QuadTree a)
quadTree : Random.Generator a -> Random.Generator (QuadTree a)
quadTree leafGen =
let
subQuads =
Random.lazy (\_ -> quadTree leafGen)
in
Random.andThen identity <|
Random.uniform
(Random.constant Empty)
[ Random.map Leaf leafGen
, Random.map4 Node subQuad subQuad subQuad subQuad
]
We start by creating a QuadTree
type where each quadrant is either Empty
, a
Leaf
containing something interesting, or a Node
with four sub-quadrants.
Next the quadTree
definition describes how to generate these values. A third
of a time you get an Empty
tree. A third of the time you get a Leaf
with
some interesting value. And a third of the time you get a Node
full of more
QuadTree
values. How are those subtrees generated though? Well, we use our
quadTree
generator!
Exercises: Can quadTree
generate infinite QuadTree
values? Is there
some way to limit the depth of the QuadTree
? Can you render the QuadTree
to HTML using absolute positions and fractional dimensions? Can you render
the QuadTree
to SVG?
Note: Check out the docs for lazy
to learn why that is needed
to define a recursive Generator
like this one.
Combine five generators.
If you need to combine more things, you can always do it by chaining
andThen
. There are also some additional helpers for this
in elm-community/random-extra
.
Fancy Stuff
Generate fancy random values.
We have seen examples of how andThen
can be used to generate variable length
arrays in the array
and map2
docs. We saw how it could help
generate a quadtree in the map4
docs.
Anything you could ever want can be defined using this operator! As one last
example, here is how you can define map
using andThen
:
import Random
map : (a -> b) -> Random.Generator a -> Random.Generator b
map func generator =
generator
|> Random.andThen (\value -> Random.constant (func value))
The andThen
function gets used a lot in elm-community/random-extra
,
so it may be helpful to look through the implementation there for more examples.
Helper for defining self-recursive generators. Say we want to generate a random number of probabilities:
import Random
probabilities : Random.Generator (Array Float)
probabilities =
Random.andThen identity <|
Random.uniform
(Random.constant [])
[ Random.map2 (::)
(Random.float 0 1)
(Random.lazy (\_ -> probabilities))
]
In 50% of cases we end the array. In 50% of cases we generate a probability and
add it onto a random number of probabilities. The lazy
call is crucial
because it means we do not unroll the generator unless we need to.
This is a pretty subtle issue, so I recommend reading more about it here!
Note: You can delay evaluation with andThen
too. The thing that matters
is that you have a function call that delays the creation of the generator!
Constants
The underlying algorithm works well in a specific range of integers. It can generate values outside of that range, but they are “not as random”.
The maxInt
that works well is 2147483647
.
The underlying algorithm works well in a specific range of integers. It can generate values outside of that range, but they are “not as random”.
The minInt
that works well is -2147483648
.
Generate Values Manually
Maybe you do not want to use generate
for some reason? Maybe
you need to be able to exactly reproduce a sequence of random values?
In that case, you can work with a Seed
of randomness and step
it
forward by hand.
So you need reproducible randomness for some reason.
This step
function lets you use a Generator
without commands. It is a
normal Gren function. Same input, same output! So to get a 3D point you could
say:
import Random
type alias Point3D = { x : Float, y : Float, z : Float }
makePoint3D : Float -> Float -> Float -> Point3D
makePoint3D x y z =
{ x = x, y = y, z = z }
point3D : Random.Seed -> { value : Point3D, seed : Random.Seed }
point3D seed0 =
let
{ value = x, seed = seed1 } = Random.step (Random.float 0 100) seed0
{ value = y, seed = seed2 } = Random.step (Random.float 0 100) seed1
{ value = z, seed = seed3 } = Random.step (Random.float 0 100) seed2
in
{ value = makePoint3D x y z, seed = seed3 }
Notice that we use different seeds on each line! If we instead used seed0
for everything, the x
, y
, and z
values would always be exactly the same!
Same input, same output!
Threading seeds around is not super fun, so if you really need this, it is
best to build your Generator
like normal and then just step
it all at
once at the top of your program.
Create a Seed
for reproducible randomness.
import Random
seed0 : Random.Seed
seed0 =
Random.initialSeed 42
If you hard-code your Seed
like this, every run will be the same. This can
be useful if you are testing a game with randomness and want it to be easy to
reproduce past games.
In practice, you may want to get the initial seed by (1) sending it to Gren
through flags or (2) using Time.now
to get a number that the user has not
seen before. (Flags are described on this page.)
A generator that produces a seed that is independent of any other seed in the program. These seeds will generate their own unique sequences of random values. They are useful when you need an unknown amount of randomness later but can request only a fixed amount of randomness now.
The independent seeds are extremely likely to be distinct for all practical purposes. However, it is not proven that there are no pathological cases.