When I first started playing with Elm, the discoveries went something like this:
- Wow, this is some powerful functional mojo.
- I bet this would be a blast to make games with!
- I just need a random number…
- …
- I better go read half of the Internet…
- …
- Have I made bad life choices???
Laugh all you want, but back then Elm’s documentation of the Random
module opened with a twenty line example. I was using Ruby everyday where I had rand()
!
Why are functional programming and random number generation mortal enemies? The answer is kind of interesting…
Seeds
To make sense of why this topic is complicated, let’s begin with some exploration in a seemingly less strict language. Here’s the easiest way to start pulling random numbers out of Elixir (or Erlang):
$ iex
Erlang/OTP 18 [erts-7.3] [source] [64-bit] [smp:8:8] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]
Interactive Elixir (1.2.5) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> :rand.uniform
0.006229498385012341
iex(2)> :rand.uniform
0.8873819908035944
iex(3)> :rand.uniform
0.23952446122301357
To prove those are really random, I’ll repeat the process and hope we see new numbers:
$ iex
Erlang/OTP 18 [erts-7.3] [source] [64-bit] [smp:8:8] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]
Interactive Elixir (1.2.5) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> :rand.uniform
0.9471495706406268
iex(2)> :rand.uniform
0.9593850716627006
iex(3)> :rand.uniform
0.19095631066267954
OK, they look different. I better pull a rabbit out of my hat quick, because this is shaping up to be one lame blog post.
Let’s ask for a random number one more time, but let’s keep an eye on the process dictionary as we do it this time. What’s a process dictionary you ask? It’s Elixir’s dirty little secret. Inside every process, there’s some mutable data storage. Oh yeah, believe it:
$ iex
Erlang/OTP 18 [erts-7.3] [source] [64-bit] [smp:8:8] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]
Interactive Elixir (1.2.5) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> Process.get_keys
[:iex_history]
iex(2)> :rand.uniform
0.6585831964907924
iex(3)> Process.get_keys
[:iex_history, :rand_seed]
Look a rabbit, err, a :rand_seed
!
Before we asked for a random number, the process dictionary only contained some data from my REPL session. But after the call, there was random number related data in there.
Let’s dig a little deeper by watching the seed as well pull another number:
iex(4)> seed = Process.get(:rand_seed)
{%{max: 288230376151711743, next: #Function,
type: :exsplus, uniform: #Function,
uniform_n: #Function},
[41999190440545321 | 147824492011192469]}
iex(5)> :rand.uniform
0.5316998602996328
iex(6)> Process.get(:rand_seed)
{%{max: 288230376151711743, next: #Function,
type: :exsplus, uniform: #Function,
uniform_n: #Function},
[147824492011192469 | 5427558722783275]}
What did we see there? First, seeds are gross looking things. More importantly though, it’s changing as we pull numbers. You can tell by comparing the numbers in the improper list at the end of the seed.
What are these changing seeds?
You can kind of think of random number generation as a giant list of good numbers available for picking. (Yes, I’m simplifying here, but stick with me for a moment…) It’s like someone sat down at the dawn of computing, rolled a dice way too many times, and carved all the numbers into stone tablets for us to use later on. The problem is, if we always started at the beginning of the list, the sequence would always be the same and video games would be hella boring!
How do we make use of this master list? We seed it! You can think of that as your computer closing its eyes, pointing its finger at a random spot in the list, and saying, “I’ll pick this number next.” Except that your computer probably doesn’t have eyes or a finger. Anyway, that’s how you get random numbers out of a random list… randomly. (At least it’s close enough for our purposes. I know I lied some. I’m sorry. It’s for a good cause.)
Getting back to our example, it turns out there’s another function that we could call to get random numbers. We were using :rand.uniform/0
, but I think it’s time to try :rand.uniform_s/1
. What does the _s
stand for? I have no idea. Let’s pretend it’s “seeded” solely because it helps my story here and you want me to succeed. :rand.uniform_s/1
expects to be passed a seed and I stuck the first one we examined in a seed
variable. Right after we saw that seed last time, we got the number 0.5316998602996328
out of the generator. You can scroll up if you think I would make something like that up. Or you could scroll down:
iex(7)> :rand.uniform_s(seed)
{0.5316998602996328,
{%{max: 288230376151711743, next: #Function,
type: :exsplus, uniform: #Function,
uniform_n: #Function},
[147824492011192469 | 5427558722783275]}}
The return value has changed from what :rand.uniform/0
gives us, but the first item in the tuple is the random number that we saw before. The second item in the tuple is a new seed. You’ve seen it before too. It was in our process dictionary after we generated 0.5316998602996328
last time. I can’t believe you didn’t recognize it!
When using the _s
functions, Elixir leaves it to us to track the seeds. Anytime we fetch a number, it gives us that and a new seed for the next time we need one. It’s kind of like generating two numbers each time, one to use and one to be the new finger. Well, you know what I mean!
In Elixir, asking for a seed is an impure function that returns a different answer with each call. (Oops, I lied again! More on that later…) Again, compare the numbers at the end of each seed to see the differences:
$ iex
Erlang/OTP 18 [erts-7.3] [source] [64-bit] [smp:8:8] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]
Interactive Elixir (1.2.5) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> Process.get_keys
[:iex_history]
iex(2)> :rand.seed_s(:exsplus)
{%{max: 288230376151711743, next: #Function,
type: :exsplus, uniform: #Function,
uniform_n: #Function},
[88873856995298220 | 199352771829937909]}
iex(3)> :rand.seed_s(:exsplus)
{%{max: 288230376151711743, next: #Function,
type: :exsplus, uniform: #Function,
uniform_n: #Function},
[12382884451496331 | 275984276003718626]}
iex(4)> :rand.seed_s(:exsplus)
{%{max: 288230376151711743, next: #Function,
type: :exsplus, uniform: #Function,
uniform_n: #Function},
[252423044625965483 | 35827924244841321]}
The :exsplus
argument specifies the algorithm to use, which is probably the source of those anonymous functions we keep seeing in the seeds. I’ve used the default that Elixir would pick itself, if we let it choose the seed.
Now, using seeds is a pure function call that always yields the same results:
iex(5)> seed = :rand.seed_s(:exsplus)
{%{max: 288230376151711743, next: #Function,
type: :exsplus, uniform: #Function,
uniform_n: #Function},
[256126833480978113 | 32213264852888463]}
iex(6)> :rand.uniform_s(seed)
{0.2372892113261949,
{%{max: 288230376151711743, next: #Function,
type: :exsplus, uniform: #Function,
uniform_n: #Function},
[32213264852888463 | 36180693784403711]}}
iex(7)> :rand.uniform_s(seed)
{0.2372892113261949,
{%{max: 288230376151711743, next: #Function,
type: :exsplus, uniform: #Function,
uniform_n: #Function},
[32213264852888463 | 36180693784403711]}}
iex(8)> Process.get_keys
[:iex_history]
Also, notice that we’re no longer using secret mutable storage to generate numbers this way.
How do we use all of this in actual work? We don’t. We typically just cheat and call :rand.uniform/0
. But now you know how the cheating works!
For the sake of example, and blog post line count, here’s a simple script doing things The Right Way™. It streams random numbers, a new one each second:
defmodule ARandomNumberASecond do
defstruct [:callback, :rand_seed]
def generate(callback) when is_function(callback) do
state = %__MODULE__{callback: callback, rand_seed: :rand.seed_s(:exsplus)}
do_generate(state)
end
defp do_generate(
state = %__MODULE__{callback: callback, rand_seed: rand_seed}
) do
{number, new_rand_seed} = :rand.uniform_s(rand_seed)
callback.(number)
:timer.sleep(1_000)
new_state = %__MODULE__{state | rand_seed: new_rand_seed}
do_generate(new_state)
end
end
ARandomNumberASecond.generate(fn n -> IO.puts n end)
The main trick here is that we keep passing forward a fresh seed. The first one is created in ARandomNumberASecond.generate/1
. After that, we keep track of each seed returned from :rand.uniform_s/1
and use that in the next call. Look Ma, no mutable state!
Calling this script will give you a a list that looks something like the following. If your output is exactly the same, you should have bought a lottery ticket this week:
$ elixir a_random_number_a_second.exs
0.8858439425003749
0.8401632592437187
0.22632370083022735
0.8504749565721951
0.22907809547742136
0.302700583368534
0.756432821571183
0.8986912440205949
0.10409795947101291
0.04221231517545583
…
Side Effects
Meanwhile, back in Elm, the evil wizard Random
has driven all side effects from the land…
A major goal of functional programming is to limit side effects. We want to be working with as many side effect free, pure functions as possible. Side effects are much harder to reason about and control, so they end up being a major source of bugs in our code.
Elm, in particular, takes this call to arms very seriously. The entire process of seeding and generating random numbers is side effect free. Let’s get into how that works:
$ elm repl
---- elm repl 0.17.0 -----------------------------------------------------------
:help for help, :exit to exit, more at <https:>
--------------------------------------------------------------------------------
> import Random
> Random.initialSeed 1
Seed { state = State 2 1, next = <function>, split = <function>, range = <function> }
: Random.Seed
> Random.initialSeed 1
Seed { state = State 2 1, next = <function>, split = <function>, range = <function> }
: Random.Seed
> Random.initialSeed 2
Seed { state = State 3 1, next = <function>, split = <function>, range = <function> }
: Random.Seed
You can see here that you need to provide some number when requesting a seed in Elm. If you use the same number, you get the same seed (compare the State
numbers). Different numbers yield different seeds.
This makes testing code that makes use of random numbers quite easy. You can create the seed with a known value in tests, pass that into your code, and magically predict the balls as they fall out of the tumbler. That’s a powerful benefit.
A quick aside in defense of Elixir’s honor: it also supports seeding from known values. I just didn’t show it. Throwing Elixir under the bus: you seed it with a tuple of three integers because that’s maximally weird.
Back to Elm. Again.
Elm has another abstraction at play: generators. A generator knows how to build whatever you are after whether that’s integers in some range, booleans, or whatever. They can also be combined, so Random.list
could fill a list of a desired size with integers or booleans. Here’s how you build them:
> Random.int -1 1
Generator <function> : Random.Generator Int
> Random.list 10 (Random.int -1 1)
Generator <function> : Random.Generator (List Int)
OK, that’s pretty dull, but we can combine generators and seeds to produce numbers:
> Random.step (Random.int -1 1) (Random.initialSeed 1)
(1,Seed { state = State 80028 40692, next = <function>, split = <function>, range = <function> })
: ( Int, Random.Seed )
> Random.step (Random.int -1 1) (Random.initialSeed 1)
(1,Seed { state = State 80028 40692, next = <function>, split = <function>, range = <function> })
: ( Int, Random.Seed )
> Random.step (Random.int -1 1) (Random.initialSeed 1)
(1,Seed { state = State 80028 40692, next = <function>, split = <function>, range = <function> })
: ( Int, Random.Seed )
> Random.step (Random.int -1 1) (Random.initialSeed 61676)
(0,Seed { state = State 320459915 40692, next = <function>, split = <function>, range = <function> })
: ( Int, Random.Seed )
> Random.step (Random.list 10 (Random.int -1 1)) (Random.initialSeed 100)
([1,1,0,1,1,-1,-1,1,1,0],Seed { state = State 1625107866 1858572493, next = <function>, split = <function>, range = <function> })
: ( List Int, Random.Seed )
> Random.step (Random.list 10 (Random.int -1 1)) (Random.initialSeed 42)
([1,-1,-1,-1,0,-1,1,-1,-1,-1],Seed { state = State 2052659270 1858572493, next = <function>, split = <function>, range = <function> })
: ( List Int, Random.Seed )
Note that we get generated item and new seed pairs, just like we saw in Elixir. You can also see that the same seed always produces the same value.
That leaves us with just one question, but it’s a big one: where do we get the number needed to create the initial seed? Hardcoding some value works for your tests and this blog post, but doing it for your application proper kind of defeats the point of randomization, am I right?
In my next blog post… OK, OK, we’ll discuss it now. Calm down.
To get an initial seed we’re obviously going to need to do something in a place more tolerant of side effects. It just so happens that Elm code is bootstrapped in JavaScript, where side effects are perfectly legal. We can use JavaScript’s random number generator to kick start random number generation inside of Elm’s side effect free walls.
To show what that process might look like, let’s recreate our number a second streamer in Elm. We begin with this HTML:
<meta charset="utf-8"><title>A Random Number A Second</title><script src="ARandomNumberASecond.js" type="text/javascript" charset="utf-8"></script><script type="text/javascript" charset="utf-8">
Elm.ARandomNumberASecond.fullscreen({
randSeed: Math.floor(Math.random() * 0xFFFFFFFF)
});
</script>
The key bit here is that we pass a program flag into Elm’s invocation. This is a randSeed
pulled from JavaScript’s random number generator.
Now we need to dig into the Elm code to see how to make use of that flag. We’ll start with general program concerns:
module ARandomNumberASecond exposing (main)
import Html exposing (..)
import Html.App
import Time exposing (Time, second)
import Random
type alias Flags = {randSeed : Int}
main : Program Flags
main =
Html.App.programWithFlags
{ init = init
, update = update
, view = view
, subscriptions = subscriptions
}
This code just import
s what we need and sets up execution in version 0.17 style. Note that we do tell Elm that this is a programWithFlags
. The model is where we can finally make use of that value:
-- MODEL
type alias Model =
{ currentSeed : Random.Seed
, numbers : List Int
}
init : Flags -> (Model, Cmd Msg)
init {randSeed} =
( { currentSeed = Random.initialSeed randSeed
, numbers = [ ]
}
, Cmd.none
)
Elm will pass all flags to your init
function. Here that allows us to construct the initial seed. We bootstrap the Model
to keep track of two bits of state: the current Random.seed
and our List
of generated numbers for display.
Here’s the event handling code:
-- UPDATE
type Msg = Tick Time
update : Msg -> Model -> (Model, Cmd Msg)
update _ {currentSeed, numbers} =
let
(number, nextSeed) = Random.step (Random.int 1 100) currentSeed
in
( { currentSeed = nextSeed
, numbers = numbers ++ [number]
}
, Cmd.none
)
We expect to receive Msg
s that have the current time inside of them, one each second. We can actually ignore the time, but use its arrival as our cue to append a new Random
number onto our list. This code pushes nextSeed
forward, just as we did in Elixir.
Next, we need to sign up to receive those Tick
Msg
s:
-- SUBSCRIPTIONS
subscriptions : Model -> Sub Msg
subscriptions _ =
Time.every second Tick
This code subscribes to receive the Time
from the Elm runtime each second
, wrapped in a Tick
. This passes impure responsibilities on to Elm itself and leaves our code pure.
The final bit is just to render the current list of numbers:
-- VIEW
view : Model -> Html Msg
view {currentSeed, numbers} =
case numbers of
[ ] ->
viewLoading
_ ->
viewNumbers numbers
viewLoading : Html Msg
viewLoading =
text "Loading..."
viewNumbers : List Int -> Html Msg
viewNumbers numbers =
div [ ] (List.map viewNumber numbers)
viewNumber : Int -> Html Msg
viewNumber n =
p [ ] [text (toString n)]
That is some very basic HTML generation because this is a programming blog post. Had this been a design blog post, it would have looked gorgeous. Also, it wouldn’t have been written by me.
The only trick in the code above is that we render a "Loading..."
message until the first time event shows up about one second into our run. We detect that case by pattern matching on our still empty list of numbers
.
You can find the full code for this example in this Gist, if you want to play with it yourself.
When I run the code, my browser begins filling up with numbers:
96
100
17
86
32
60
98
59
97
80
…
It’s worth noting that this isn’t the only way to generate random numbers in Elm 0.17. You can instead generate Cmd
s that the Elm runtime transforms into events your code receives. This pushes seeding concerns out of your code while still keeping it pure. The documentation has an example of such usage. I’ve used the “manual” approach in this code because it’s harder and I wanted to impress you.
That’s the show, folks. Hopefully you’ve seen how random number generation works in both Elixir and Elm. Maybe you even gained some insight into why this process works the way it does. If you have, please write to me and explain it. Thanks!