Maybe and Either
Prerequisites: Algebraic data types
Haskell has no null. When a value might be missing, the type says so.
Maybe
-- illustrative: this is how the standard library defines it
data Maybe a = Nothing | Just a
import Data.Maybe (fromMaybe, mapMaybe, catMaybes)
import Text.Read (readMaybe)
safeDiv :: Int -> Int -> Maybe Int
safeDiv _ 0 = Nothing
safeDiv a b = Just (a `div` b)
describe :: Maybe Int -> String
describe Nothing = "no result"
describe (Just n) = "result: " ++ show n
main :: IO ()
main = do
putStrLn (describe (safeDiv 10 2))
putStrLn (describe (safeDiv 1 0))
print (fromMaybe 0 (safeDiv 1 0))
print (maybe "none" show (Just 42))
print (mapMaybe (\s -> readMaybe s :: Maybe Int) ["1", "x", "3"])
print (catMaybes [Just 1, Nothing, Just 3])
print (lookup "b" [("a", 1), ("b", 2)])
result: 5
no result
0
"42"
[1,3]
[1,3]
Just 2
- A function returning
Maybe Intmust be handled by the caller: you can't accidentally use aMaybe Intas anInt. That's how Haskell eliminates null-pointer errors. fromMaybe default mandmaybe default f munwrap with a fallback.mapMaybemaps and drops theNothings in one step.
Either
Maybe says that something failed; Either says why:
-- illustrative: the standard definition
data Either a b = Left a | Right b
By convention Left holds an error and Right holds the result ("right"
as in correct):
data AgeError = NotANumber String | Negative Int | TooOld Int
deriving (Show)
parseAge :: String -> Either AgeError Int
parseAge s = case reads s of
[(n, "")]
| n < 0 -> Left (Negative n)
| n > 150 -> Left (TooOld n)
| otherwise -> Right n
_ -> Left (NotANumber s)
main :: IO ()
main = do
print (parseAge "36")
print (parseAge "-3")
print (parseAge "200")
print (parseAge "abc")
print (either show (\n -> "ok: " ++ show n) (parseAge "36"))
Right 36
Left (Negative (-3))
Left (TooOld 200)
Left (NotANumber "abc")
"ok: 36"
Using your own error type (AgeError) instead of a String means
callers can pattern match on exactly what went wrong.
Chaining without nesting
Handling each Nothing with a case quickly nests. The next lessons
show how Functor, Applicative, and Monad chain these computations
cleanly — for now, a preview:
import Text.Read (readMaybe)
addStrings :: String -> String -> Maybe Int
addStrings a b = do
x <- readMaybe a
y <- readMaybe b
return (x + y)
main :: IO ()
main = do
print (addStrings "2" "40")
print (addStrings "2" "forty")
print (fmap (* 2) (Just 21))
Just 42
Nothing
Just 42
The do block stops at the first Nothing — no explicit checks needed.
Practice
Write safeRoot :: Double -> Either String Double that fails with a
message for negative numbers, and print it for 16 and -4.
Show solution
safeRoot :: Double -> Either String Double
safeRoot x
| x < 0 = Left ("cannot take the root of " ++ show x)
| otherwise = Right (sqrt x)
main :: IO ()
main = do
print (safeRoot 16)
print (safeRoot (-4))
Right 4.0
Left "cannot take the root of -4.0"