Random
This library helps you generate pseudo-random values.
It is an implementation of Permuted Congruential Generators by M. E. O'Neil. It is not cryptographically secure.
Generators
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: If you are not familiar with commands yet, start working through guide.elm-lang.org. It builds up to an example that generates random numbers. Commands are one of the core concepts in Gren, and it will be faster overall to invest in understanding them now rather than copy/pasting your way to frustration! And if you feel stuck on something, definitely ask about it in the Gren slack. Folks are happy to help!
Note 2: The random Generator API is quite similar to the JSON Decoder API.
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.