Learn You a Haskell for Great Good! - Miran Lipovaca [125]
Code, Code, Code
We can represent the pole with a simple pair of integers. The first component will signify the number of birds on the left side and the second component the number of birds on the right side:
type Birds = Int
type Pole = (Birds, Birds)
First, we made a type synonym for Int, called Birds, because we’re using integers to represent how many birds there are. And then we made a type synonym (Birds, Birds) and called it Pole (not to be confused with a person of Polish descent).
Now, how about adding functions that take a number of birds and land them on one side of the pole or the other?
landLeft :: Birds -> Pole -> Pole
landLeft n (left, right) = (left + n, right)
landRight :: Birds -> Pole -> Pole
landRight n (left, right) = (left, right + n)
Let’s try them out:
ghci> landLeft 2 (0, 0)
(2,0)
ghci> landRight 1 (1, 2)
(1,3)
ghci> landRight (-1) (1, 2)
(1,1)
To make birds fly away, we just had a negative number of birds land on one side. Because landing a bird on the Pole returns a Pole, we can chain applications of landLeft and landRight:
ghci> landLeft 2 (landRight 1 (landLeft 1 (0, 0)))
(3,1)
When we apply the function landLeft 1 to (0, 0) we get (1, 0). Then we land a bird on the right side, resulting in (1, 1). Finally, two birds land on the left side, resulting in (3, 1). We apply a function to something by first writing the function and then writing its parameter, but here it would be better if the pole went first and then the landing function. Suppose we make a function like this:
x -: f = f x
We can apply functions by first writing the parameter and then the function:
ghci> 100 -: (*3)
300
ghci> True -: not
False
ghci> (0, 0) -: landLeft 2
(2,0)
By using this form, we can repeatedly land birds on the pole in a more readable manner:
ghci> (0, 0) -: landLeft 1 -: landRight 1 -: landLeft 2
(3,1)
Pretty cool! This version is equivalent to the one before where we repeatedly landed birds on the pole, but it looks neater. Here, it’s more obvious that we start off with (0, 0) and then land one bird on the left, then one on the right, and finally, two on the left.
I'll Fly Away
So far so good, but what happens if ten birds land on one side?
ghci> landLeft 10 (0, 3)
(10,3)
Ten birds on the left side and only three on the right? That’s sure to send poor Pierre falling through the air! This is pretty obvious here, but what if we had a sequence of landings like this:
ghci> (0, 0) -: landLeft 1 -: landRight 4 -: landLeft (-1) -: landRight (-2)
(0,2)
It might seem as if everything is okay, but if you follow the steps here, you’ll see that at one time there are four birds on the right side and no birds on the left! To fix this, we need to take another look at our landLeft and landRight functions.
We want the landLeft and landRight functions to be able to fail. We want them to return a new pole if the balance is okay but fail if the birds land in a lopsided manner. And what better way to add a context of failure to value than by using Maybe! Let’s rework these functions:
landLeft :: Birds -> Pole -> Maybe Pole
landLeft n (left, right)
| abs ((left + n) - right) < 4 = Just (left + n, right)
| otherwise = Nothing
landRight :: Birds -> Pole -> Maybe Pole
landRight n (left, right)
| abs (left - (right + n)) < 4 = Just (left, right + n)
| otherwise = Nothing
Instead of returning a Pole, these functions now return a Maybe Pole. They still take the number of birds and the old pole as before, but then they check if landing that many birds on the pole would throw Pierre off balance. We use guards to check if the difference between the number of birds on the new pole is less than 4. If it is, we wrap the new pole in a Just and return that.