# Maybe and Either — Haskell

Source: https://www.geekswithgeeks.com/en/haskell/t-maybe

> Represent missing values and errors with Maybe and Either.

## No null, no surprise exceptions

Haskell has no `null`. A value that may be absent has type **`Maybe a`**, which is either **`Just x`** or **`Nothing`**, and the compiler forces you to handle both cases. Functions such as `lookup`, `Data.Map.lookup`, `find` and `Text.Read.readMaybe` return `Maybe`. **`Either e a`** represents a computation that may fail with an error: **`Left err`** or **`Right value`**, by convention with the error on the left. Useful helpers: **`maybe default f m`** and **`fromMaybe`**, **`either onLeft onRight`**, **`mapMaybe`** and **`catMaybes`** to drop missing values, and **`traverse`** to validate a whole list. Because `Maybe` and `Either` are **monads**, you can chain several fallible steps with **`do` notation**, and the chain stops at the first `Nothing` or `Left` (the next section explains why). Use `Maybe` when the reason for absence is obvious, and `Either` with a descriptive error type (often a sum type of error cases) when callers need to know what went wrong. Reserve exceptions for truly exceptional situations in `IO`.

## Validating a signup with Either

Each step can fail; do notation stops at the first Left.

```haskell
import Text.Read (readMaybe)
import Data.Maybe (mapMaybe, fromMaybe)
import Data.Char (isSpace)

data SignupError = MissingName | InvalidEmail String | InvalidAge String
  deriving (Show, Eq)

data Signup = Signup { name :: String, email :: String, age :: Int }
  deriving Show

trim :: String -> String
trim = reverse . dropWhile isSpace . reverse . dropWhile isSpace

validName :: String -> Either SignupError String
validName n = if null (trim n) then Left MissingName else Right (trim n)

validEmail :: String -> Either SignupError String
validEmail e = if '@' `elem` e then Right e else Left (InvalidEmail e)

validAge :: String -> Either SignupError Int
validAge raw = case readMaybe raw of
  Just a | a >= 13 && a <= 120 -> Right a
  _                            -> Left (InvalidAge raw)

signup :: String -> String -> String -> Either SignupError Signup
signup n e a = do
  n' <- validName n
  e' <- validEmail e
  a' <- validAge a
  pure (Signup n' e' a')

main :: IO ()
main = do
  print (signup "Asha" "asha@example.com" "29")   -- Right (Signup {...})
  print (signup "Ravi" "ravi" "x")                -- Left (InvalidEmail "ravi")
  print (mapMaybe readMaybe ["1", "two", "3"] :: [Int])   -- [1,3]
  print (fromMaybe 0 (lookup "ink" [("pen", 120), ("ink", 40 :: Int)]))   -- 40
```

## Avoid fromJust

`fromJust Nothing` crashes, reintroducing the null-pointer problem `Maybe` was meant to solve. Use pattern matching, `maybe`, `fromMaybe` or do notation instead.

**Quiz:** In `do { a <- step1; b <- step2 a; pure (a, b) }` with Either, what happens if step1 returns Left err?

- [ ] step2 still runs
- [ ] It throws an exception
- [x] The whole expression is Left err and later steps do not run
- [ ] It returns Right ()

*Answer:* The whole expression is Left err and later steps do not run. Either's monad short-circuits on the first Left.
