Randomness in Elm

Explaining Elm Generators by Example

When writing Elm code nearly every line of code you write will be inside of a pure function. But that begs the question, just how do you invoke the equivalent of Math.random() (an impure function) in Elm? Surely even a pure functional language can still use randomness, right?

Indeed it can! Let’s start off by exploring how randomness can be achieved in Elm by looking into the bottom type behind it all, generators.

Generators

Generators are behind all randomness in Elm. A generator is a type that describes how to produce random values.

A generator is a type that describes how to produce random values.

For example, here is a generator that describes a random integer from 0 to 100.

Here is another generator that leverages Random.map to describe a randomly selected suit for a deck of playing cards.

Remember, suitGenerator and zeroTo100Generator are values of type Generator. A generator on its own cannot produce random values. For that you need to pass the generator to one of two functions: generate or step .

Generate vs Step

Generate and step are the two functions that allow you to produce random values in the purely functional Elm. I like to think of them as randomness as a side effect and randomness as a pure function.

Randomness as a side effect

Let’s start with generate’s function annotation.

Generate is a function that takes a Generator and a mapping function and produces a Cmd msg . In Elm, Cmd’s are used to describe side effects that are then performed by the Elm runtime.

Note that there are a few moving pieces in this example.

When the user clicks the button a Msg is dispatched from onClick The Elm runtime passes that Msg to the update function The update function has a branch for that Msg and it produces a Cmd using Random.generate . This tells the Elm runtime that we want to produce a side effect. The Elm runtime produces a random value and sends a different Msg back to the update function which then updates the model with the random value.

Randomness as a pure function

Step, on the other hand, has a different signature.

The step function takes a generator and a seed and produces a tuple containing the randomly produced value and a new seed. Contrary to generate , where randomness is treated as a side effect and handed to the Elm runtime, with step randomness is treated as a pure function.

This seems bizarre since calling Math.random() multiple times in JavaScript produces new random values, thus making it an impure function. But because we are providing a seed to a deterministic function the step function is still pure. For a given seed and generator it will always produce the same random value.

Which one is better?

Given these two options you’re probably wondering which is better between generate and step . Does it matter if you’re producing one random value? What about multiple random values? Or what about a random value that depends on the result of another random value?

The short answer is generate is probably better.

The longer answer is, from personal experience writing an app where you have to manage your own random seeds with step can be a huge pain and prone to errors. Realize that when you call step with a seed it gives you another seed that you can pass to subsequent step invocations. That means that a potentially globally tracked seed is leaking into the depths of your functions.

Generally, generate is a pretty good option. Even if you need to produce multiple random values that depend on each other you can get away with a single call to generate by composing generators.

Building generators by example

Let’s go over a few more examples to help give you a better idea of how to build larger generators that produce more interesting random values.

Random.map

With Random.map we can take the output of one or more generators and transform it into a generator for a different value. Let’s create a generator that will return a Jump message most of the time and a Crouch message otherwise.

Random.andThen

Random.andThen takes the random value from one generator and returns a new generator. This is useful for producing different generators depending on the result of another random value.

Let’s say I want to randomly produce two different values. Fifty percent of the time I want to produce a random number between 1 and 10 and fifty percent of the time I want a random number between 11 and 20. This is a case where one random value depends on another.

Advanced example

For this last example I’m going to show the most advanced example of randomness in Elm that I can think of. It is not for the faint of heart. Last year I wrote a package called elm-genetic that provides some of the basic boilerplate for using a genetic algorithm.

For the purposes of this post, a genetic algorithm uses quite a bit of randomness to mimic the process of biological evolution to solve a problem.

The “problem” here is replicating another image as closely as possible

There are many elements of randomness involved:

What random solutions are in Generation 0

Which random traits are mutated

Given a random trait, how that trait will be randomly mutated

Which random parents “breed” together

The actual algorithm has a fair amount of boilerplate (hence the idea to extract the common parts to a package) but the algorithm needs to know about a few pieces of information.

Right off the bat it needs to know two pieces of information that involve randomness. It needs to know how to generate the DNA for a random solution (to populate Generation 0). It also need to know how to mutate the DNA of a given solution.

And the code for mutateDna ..

Actually, there’s a lot more to it than that.

Both of these Generators are provided to the genetic algorithm but like what was mentioned earlier the genetic algorithm still has other elements of randomness too. If we pop open the hood of the elm-genetic package we’ll see a lot of generator composition.

For example, this code shows a generator that ultimately randomly “breeds” two parents to produce a child, randomly mutates that child’s DNA, and based on the outcome evaluates how good its DNA is.

And the code to generate an initial random population of possible solutions..

You can see this live at http://ckoster22.github.io/genetic/bugsbunny.html (it takes a while to run)

If looking at that is too much I suggest checking out the Hello World example which is published at http://ckoster22.github.io/genetic/hello.html

And there are a few more examples in the source.

If this is your first go with randomness in Elm that file will be a bit overwhelming so if you have any questions I’m available on Elm slack as charliek. Feel free to ping me with your questions.

Additional resources

Elm guide on randomness