Maybe: when there may be no answer
A lookup that can fail should say so in its type: Maybe holds either nothing at all or a real answer, and you take it apart before you use it.
The absence goes in the type
A search sometimes finds nothing, and the type is the right place to admit it. Maybe Int means: either Nothing, meaning there is no answer, or Just x, carrying the answer x inside.
lookup searches a list of pairs and is already part of the standard library, so no import is needed for it. It answers a Maybe, and the type of the answer is what tells you, at a glance, that this search can come back empty.
It walks the list from the front and answers the first pair whose key matches. A list with the same key twice therefore hides the later value rather than replacing it, and the answer depends on the order of the list. A map would keep one entry per key instead, so the two are not interchangeable when duplicates are possible. Decide before the lookup which one should win, or clean the list first. The type is Eq a => a -> [(a, b)] -> Maybe b, so it works for any key that can be compared for equality.
This is not a null
Nothing is a real value of the type, not a broken reference. You cannot add it, print it as a number or compare it with an Int, because it is not an Int: the compiler asks you to take the Maybe apart first.
Many languages answer a failed search with a special empty value that behaves like every other value until the day it quietly breaks something far away from the search. Here the possibility is written down, so the reader of the signature knows about it before opening the function.
Taking it apart
Two equations can handle the two cases: one for Nothing, one for Just x. A case expression does the same work in the middle of a longer function, and a helper called maybe takes the answer to use for Nothing, a function to apply to a real answer, and the Maybe value itself.
All three are the same idea at different sizes: decide what absence means, in one place, instead of leaving it to whoever calls the function.
Two ways to read a Maybe
safeHead :: [a] -> Maybe a
safeHead [] = Nothing
safeHead (x : _) = Just x
describe :: Maybe Int -> String
describe Nothing = "nothing"
describe (Just n) = "got " ++ show nFour names, three answers
Run it and read the four lines. Then add a name to the list and give it a mark of 100, and check that the first line changes.
A case that mentions only Just still compiles, with a warning, and then stops the program the first time a Nothing really arrives. Write the branch for Nothing even when you are sure it cannot happen: the type already told you that it can.
Nothing is a value, not a hole
Just 0 and Nothing are different values, and neither is the number zero. Just 0 says the search succeeded and the answer is 0; Nothing says the search did not succeed at all. A program that treats them alike has thrown away the information the type was carrying.
Because the two are different constructors of one type, a pattern match can tell them apart and the compiler can check that both are handled. You cannot add one to a number, compare it with a number, or drop it into a sentence without deciding what the empty case means.
That is also why Maybe is not a null. A null is a value of the same type that behaves like a real one until it is used; Nothing is a different shape, and the only way to reach the answer inside a Just is to match on it.
Safe functions make their callers decide
A function that answers a Maybe is total: every input, including the awkward one, produces a value. safeDiv a 0 answers Nothing rather than stopping the program, so the caller keeps control.
The cost is that the caller now has a decision to make, and that is the trade: the failure is visible in the type and is handled once, at the place that knows what to do about it, instead of becoming an exception that surfaces somewhere else.
When the answer does not matter, maybe collapses the decision into one line: a default for Nothing, a function for the value inside Just, and the value itself. It is the same two branches, written shorter.
Writing only the Just equation compiles, and then stops the program the first time a Nothing arrives. Comparing a Maybe Int with a bare number, as in safeDiv 6 2 == 3, is a type error because the two sides are different types; compare with Just 3 or take the value out first. And the arguments of maybe are ordered default, function, value, which is easy to get backwards.
Try these next
- Write
safeLast :: [a] -> Maybe awith two equations and test it on an empty list. - Use
maybeto print a default name for a lookup that fails, then for one that succeeds. - Change
safeDivso division by zero answersJust 0, and say what the caller can no longer tell.
A division that admits it can fail
Run it and read the five lines. Then write `safeLast` and use `report` on a list of one value and on an empty list.
Check what you learned
One question at a time. A wrong answer explains itself — take another go.
Part 1 of 5
What does the type Maybe Int say to the reader?
