The interesting bit I assume is this:
toss2Dice = do
n <- tossDie
m <- tossDie
return (n+m)
This is somewhat equivalent to the following Python:
def toss2dice():
for n in tossDie:
for m in tossDie:
yield (n+m)
When it comes to the list monad, you can view the binding arrows (<-
) in do notation as traditional imperative "foreach" loops. Everything after
n <- tossDie
belongs to the "loop body" of that foreach loop, and so will be evaluated once for every value in tossDie
assigned to n
.
If you want the desugaring from do
notation to actual bind operators >>=
, it looks like this:
toss2Dice =
tossDie >>= (\n ->
tossDie >>= (\m ->
return (n+m)
)
)
Notice how the "inner loop body"
(\n ->
tossDie >>= (\m ->
return (n+m)
)
)
Will get executed once for every value in tossDie
. This is pretty much the equivalent to the nested Python loops.
Technical mumbo-jumbo: The reason you get "foreach" loops from the binding arrows has to do with the particular monad you are working with. The arrows mean different things for different monads, and to know what they mean for a particular monad you have to do some sleuthing and figure out how that monad works in general.
The arrows get desugared into calls to the bind operator, >>=
, which works differently for different monads as well – this is the reason the bind arrows <-
also work differently for different monads!
In the case of the list monad, the bind operator >>=
takes a list to the left and a function returning a list to the right, and sort of applies that function to every element of the list. If we want to double every element in a list in a cumbersome way, we could imagine doing it as
λ> [1, 2, 3, 4] >>= \n -> return (n*2)
[2,4,6,8]
(return
is required to make the types work out. >>=
expects a function that returns a list, and return
will, for the list monad, wrap a value in a list.) To illustrate a perhaps more powerful example, we can start by imagining the function
λ> let posneg n = [n, -n]
λ> posneg 5
[5,-5]
Then we can write
λ> [1, 2, 3, 4] >>= posneg
[1,-1,2,-2,3,-3,4,-4]
to count the natural numbers between -4 and 4.
The reason the list monad works this way is that this particular behaviour of the bind operator >>=
and return
makes the monad laws hold. The monad laws are important for us (and perhaps the adventurous compiler) because they let us change code in ways that we know will not break anything.
A very cute side effect of this is that it makes lists very handy to represent uncertainty in values: Say you are building an OCR thingey that's supposed to look at an image and turn it into text. You might encounter a character that could be either 4 or A or H, but you are not sure. By letting the OCR thingey work in the list monad and return the list ['A', '4', 'H']
you have covered your bases. Actually working with the scanned text then become very easy and readable with do
notation for the list monad. (It sorta looks like you're working with single values, when in fact you are just generating all possible combinations!)