Any minute now… any… minute… now…

Generator-ception

Where we andThen a Generator inside a Generator

Michel Belleville
Wat, the Elm-ist
Published in
4 min readJun 11, 2019

--

me> So, Random has an andThen function… 😄

wat> That it does 😉 would you like to know more?

me> Sure. Last time we saw andThen it was for Maybe and Result, it allowed us to access what was kept inside a "successful" value of a Maybe or a Result (if any) and gave us the ability to either continue on a successful value, or switch to an error value, allowing us to chain computations that could fail. So far, so good. But this frame of reference makes no sense with a Generator that can’t ever fail now, does it?

wat> Smart question 😃 the thing is, andThen is not just about success and failure, just as map is not just about taking a value that is actually already there (but wrapped inside a constructor) so we can changing it without having to do the un-wrapping. The actual behaviour of map map changes depending on the context, and so does andThen’s.

me> This is kinda confusing… 😵

wat> It is, just as any new concept should 😉 But the good news is that it’s going to get clearer and clearer as we find more examples.

me> Ok. So, what’s the point of andThen for Generator?

wat> First, let’s remember what a Generator a is…

me> It’s sort of a template to make a random a when we ask the framework to make one for us?

wat> That’s a definition we can work with. And what did map allow us to do with a generator?

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

me> To make a new Generator b from a Generator a using an a -> b transformation function which will take the value generated by the Generator a to produce the b that will be generated by our shiny new Generator b?

wat> Exactly! 😄 You can picturemap as a way to take what’s inside a box, and change it, keeping the same box. Now, let’s get to andThen:

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

me> So… if you give me a Generator a and an a -> Generator b function, it gives us a Generator b… so it’s a bit like map, but we have to return the Generator b with the transformation? Not just a b to re-wrap it in a Generator b box?

wat> Yes. Where map only allows you to change the content of the box, andThen allows you to change the box itself.

me> Ok… but why would I need to change the box itself? 🤔

wat> Let’s see… say I want to generate a random string of random length and that I have these generators:

gimmePositiveInt : Int -> Generator Int
gimmeStringOfLength : Int -> Generator String

me> Sooo… I guess gimmePositiveInt will give me a Generator Int which gives only positive Int up to a given value, while gimmeStringOfLength generates a String that’s as long as a given Int value?

wat> Indeed. Now how would you go about generating your string of random length using only those two generators and Random.map?

me> Like this?

gimmeRandomStringOfUpTo10Chars =
gimmePositiveInt 10
|> Random.map (\n -> gimmeStringOfLength n)

wat> Well, sadly that will not give you what you think it should… what is the type of your gimmeRandomStringOfUpTo10Chars result?…

gimmeRandomStringOfUpTo10Chars : Generator (Generator String)
gimmeRandomStringOfUpTo10Chars =
gimmePositiveInt 10
|> Random.map (\n -> gimmeStringOfLength n)

wat> Generatorception 😉

me> Er… ok, I’ve got this. We gave an Int -> Generator String to map:

Random.map : (a -> b) -> Generator a -> Generator b
-- with (a -> b) being (Int -> Generator String) we get
-- a = Int
-- b = Generator String
-- so the result is now Generator (Generator String)
-- and we'd like it to be just Generator String

wat> Yup. Now, let’s try with andThen instead…

gimmeRandomStringOfUpTo10Chars : Generator String
gimmeRandomStringOfUpTo10Chars =
gimmePositiveInt 10
|> Random.andThen (\n -> gimmeStringOfLength n)
Random.andThen : (a -> Generator b) -> Generator a -> Generator b
-- with (a -> b) being (Int -> Generator String) we get
-- a = Int
-- b = Generator String
-- so the result is now Generator String
-- and that's what we wanted :)

me> Ok… the way I see it, we’ll use map when we just want to change the value the Generator produce’s without having to get another random value, and andThen when we do want another random value. Is that correct?

wat> That’s pretty much it 😃

me> And so Generator is at the same time a Functor (it has a map function) and a Monad? (it has an andThen function)

wat> Bravo 👏

me> Well, I guess I have pretty much all I need to make random stuff. Read you next time?

wat> Read you next time 😉

--

--