Learn You a Haskell for Great Good! - Miran Lipovaca [133]
in3 start = return start >>= moveKnight >>= moveKnight >>= moveKnight
Using >>= once gives us all possible moves from the start. When we use >>= the second time, for every possible first move, every possible next move is computed, and the same goes for the last move.
Putting a value in a default context by applying return to it and then feeding it to a function with >>= is the same as just normally applying the function to that value, but we did it here anyway for style.
Now, let’s make a function that takes two positions and tells us if you can get from one to the other in exactly three steps:
canReachIn3 :: KnightPos -> KnightPos -> Bool
canReachIn3 start end = end `elem` in3 start
We generate all the possible positions in three steps, and then we see if the position we’re looking for is among them. Here’s how to check if we can get from (6, 2) to (6, 1) in three moves:
ghci> (6, 2) `canReachIn3` (6, 1)
True
Yes! How about from (6, 2) to (7, 3)?
ghci> (6, 2) `canReachIn3` (7, 3)
False
No! As an exercise, you can change this function so that when you can reach one position from the other, it tells you which move to take. In Chapter 14, you’ll see how to modify this function so that we also pass it the number of moves to take, instead of that number being hardcoded as it is now.
Monad Laws
Just like functors and applicative functors, monads come with a few laws that all monad instances must abide by. Just because something is made an instance of the Monad type class doesn’t mean that it’s actually a monad. For a type to truly be a monad, the monad laws must hold for that type. These laws allow us to make reasonable assumptions about the type and its behavior.
Haskell allows any type to be an instance of any type class as long as the types check out. It can’t check if the monad laws hold for a type though, so if we’re making a new instance of the Monad type class, we need to be reasonably sure that all is well with the monad laws for that type. We can rely on the types that come with the standard library to satisfy the laws, but when we go about making our own monads, we need to manually check whether the laws hold. But don’t worry, they’re not complicated.
Left Identity
The first monad law states that if we take a value, put it in a default context with return, and then feed it to a function by using >>=, that’s the same as just taking the value and applying the function to it. To put it formally, return x >>= f is the same damn thing as f x.
If you look at monadic values as values with a context, and return as taking a value and putting it in a default minimal context that still presents that value as the function’s result, this law makes sense. If that context is really minimal, feeding this monadic value to a function shouldn’t be much different than just applying the function to the normal value—and indeed, it isn’t different at all.
For the Maybe monad, return is defined as Just. The Maybe monad is all about possible failure, and if we have a value that we want to put in such a context, treating it as a successful computation makes sense, because we know what the value is. Here are some examples of return usage with Maybe:
ghci> return 3 >>= (\x -> Just (x+100000))
Just 100003
ghci> (\x -> Just (x+100000)) 3
Just 100003
For the list monad, return puts something in a singleton list. The >>= implementation for lists goes over all the values in the list and applies the function to them. However, since there’s only one value in a singleton list, it’s the same as applying the function to that value:
ghci> return "WoM" >>= (\x -> [x,x,x])
["WoM","WoM","WoM"]
ghci> (\x -> [x,x,x]) "WoM"
["WoM","WoM","WoM"]
You’ve learned that for IO, using return makes an I/O action that has no side effects but just presents a value as its result. So it makes sense that this law holds for IO as well.
Right Identity
The second law states that if we have a monadic value and we use >>= to feed it to return, the result is our original monadic value. Formally,