Where we’re going, we won’t need side-effects.

Map to the Future

Where we make random stuff with a Generator, and work in the future

Michel Belleville
Wat, the Elm-ist
Published in
6 min readApr 23, 2019

--

me> So… what if I wanted to get a random value…

wat> Just input the time coordinates and accelerate to 88 miles per hour…

me> I want to get a random *value* not a random *answer* 😠

wat> It kinda wasn’t. Thing is, Elm can’t give you a random value.

me> But… you told me a Generator a was something that could give me a random a 😢

wat> Wait, don’t cry. It can five you a pseudo-random value. Which is usually random enough for most things that don’t have “crypto” in the title.

me> So, is there a function like JavaScript’s Math.random()?

wat> What does that Math.random() of yours do?

me> It gives me a random number each time I call it. Like…

> Math.random()
0.09275992481931072

wat> Oh, so it’s a function that returns 0.09275992481931072 every time it’s called?

me> Yes… 😄 I mean, no!!! 😅 Next time we call it, it’s going to be *another* random number. Like…

> Math.random()
0.16654248115205728
> Math.random()
0.035388785049037974

wat> 🤔 So, let me get this straight: this is a function which, given the same (absence of) parameters, returns a different value each time? 😐

me> Pretty much.

wat> Oh 😶 Then no, we can’t do that in Elm. Elm’s functions are pure. No silly side-effect. Given the same parameters, they always return the same results.

me> But you said…

wat> That you could get pseudo-random stuff, yes. That’s (part of) why the *pseudo* is here actually. What it means is that, in order to get different results every time, you need to give the (pseudo-)random generation function a parameter (called a “seed”) that has to be different every time.

me> So, every time I call the function with the same seed, I get the same result, but when I call it with a different seed every time, I get different results? Well, that’s all well and fine but since I don’t know how to get a new random seed each time that does not solve my problem now, does it? 😠

wat> 😅 now let’s not get too worked up, I have good news! Let’s say I wrote a pseudo-random generation function that generates an Int using a Seed (that is conveniently also an int) ; I could also make it return a new Seed that can be used for the next generation. Look:

gimmePseudoRandom : Seed -> (Seed, Int)
gimmePseudoRandom seed =
-- the code of this algorithm is not the subject here :)
-- just note that the result type (Seed, Int) is a tuple
-- that contains the new Seed first, and second the Int
-- we wanted generated
> gimmePseudoRandom 12345679
(791811582, 3)
-- 3 is your random result, 791811582 is the next seed
> gimmePseudoRandom 12345679
(791811582, 3)
-- given the same seed (12345679), it gives you the exact same
-- pseudo-random number (3), and the exact same seed (791811582)
> gimmePseudoRandom 791811582
(9377838711, 127)
-- now using the next seed we obtained earlier, it gives you another -- pseudo-random number (127), and another next seed (9377838711)

me> Hmm… sooo… ok, it’s not random, but it’s close enough… still, I have to give it a seed, and remember the next seed for next time I want to generate a pseudo-random number… sounds complicated. 😒

wat> Sure ; that’s probably what your JavaScript does behind your back though, I’d wager that your Math.random() function has a hidden side-effect that remembers the next seed generated every time and works with it to give you the next number…

me> Why can’t Elm be more simple, like JavaScript… 😞

wat> Don’t lament… 😃 because it kinda is!

me> But you just said?…

wat> The good people that made Elm had an idea to put that seed-y business somewhere it shouldn’t bother you too much. Like (almost) all side-effects in Elm, they are contained in the bowels of the Elm architecture, to which you can send commands (sending a Cmd msg) and receive messages from (update : msg -> model -> (model, Cmd msg)) so you can modify your state (and possibly send other commands).

me> So… you mean that I’ll somehow use Cmd msg to tell the architecture to give me pseudo-random stuff? :

wat> Exactly! Now, let’s see how it really works with an example ; say I have something like this:

import Random exposing (Generator)-- this is a simple representation of a 6-faced die
type Die = One | Two | Three | Four | Five | Six
-- this is also our model
type alias Model = Die
-- there are two messages that affect our model
type Msg
= Throw
-- one is sent when the user wants to throw the die
| NewValue Die
-- one that gives us the new face of the die
-- this is our generator for a new die value
dieGenerator : Generator Die
dieGenerator =
Random.int 1 6
|> Random.map (\intValue ->
case intValue of
1 -> One
2 -> Two
3 -> Three
4 -> Four
5 -> Five
6 -> Six
_ -> One
)
-- don't worry if it looks barbaric now, we'll explain soon :)
update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
case msg of
Throw ->
( model
-- the model stays as is since we don't have
-- a new value yet
, Random.generate NewValue dieGenerator
-- to get the new value, we send a command that will
-- return to us with the NewValue message containing
-- the new value
)
NewValue value ->
( value
-- this time we actually change the value of the die
, Cmd.none
-- and we don't have any new command to send
)

me> So… you have made a dieGenerator to make random Die values… and you feed it to Random.generate along with NewValue… why? 😳

wat> Look at the signature for Random.generate:

Random.generate : (value -> msg) -> Generator value-> Cmd msg

me> Ah, I see! 😃 You give a function that will make a msg using the value generated, a Generator that will generate that value, and you get a nicely wrapped Cmd that will return with the very msg wrapping the value as soon as it’s generated?

wat> 👍 Indeed. And since random generation never fails, you’re sure to get your msg back as soon as the value it contains is generated.

me> And when I get my NewValue message with its value inside, all I need to do is use it to change my model. Makes sense. But the dieGenerator definition is *weird*! 😲

wat> Let’s dive in. First, we use a rather simple generator that the Random module gives us for free:

Random.int : Int -> Int -> Generator Int

me> Yes, I guess it takes a lower and a higher bound and generates an Int value that’s inside the bounds?

wat> Correct 😃 now let’s see if you recognize your old friend map

Random.map : (a -> b) -> Generator a -> Generator b

me> 🤔 Soooo, it’s one of the weird cases where map works with something that’s promised rather than something that’s contained?

wat> Exactly. A Generator a does not contain the a that it will generate in the future, so you couldn’t use a simple case .. of to get the value and work with it right here and now ; yet, with Random.map you can use that future value to make the Generator b you need.

me> Ok. Let me see if I get what’s happening in the transformation function:

\intValue ->
case intValue of
1 -> One
2 -> Two
3 -> Three
4 -> Four
5 -> Five
6 -> Six
_ -> One

wat> So?

me> We get the intValue (which is an Int since we’ve generated it with a Generator Int) and when it’s 1 we return a One, when it’s 2 we return a Two… why is there a _ -> One at the bottom though? We told Random.int that we wanted an Int between 1 and 6 didn’t we?

wat> Oh, that’s because the transformation function has no way to know about those bounds. All it knows is that the value will be an Int, and a case .. of instruction has to account for all possible values, so it needs a catch-all (_) to ward against that possibility, even though we know it’ll never arise. Also… let’s say we could have used Random.uniform instead for more clarity. I’ll let you have a look at the docs.

me> Oooooh, I see there’s also a Random.andThen… Are there cases when a Generator fails?

wat> Aha, silly human, just as map is not just for values that are already there, andThen is not just for failures… but that’s a story for another time.

me> Read you then 😏

--

--