Maybe, Either and Total Functions
13.1 Maybe: Optional Values
data Maybe a = Nothing | Just a -- from the Prelude
safeDiv :: Int -> Int -> Maybe Int
safeDiv _ 0 = Nothing
safeDiv x y = Just (x `div` y)
safeHead :: [a] -> Maybe a
safeHead [] = Nothing
safeHead (x : _) = Just x
import Data.Maybe (fromMaybe, maybe, catMaybes, mapMaybe, isJust, isNothing, listToMaybe, fromJust)
fromMaybe 0 (safeDiv 10 0) -- 0
maybe "none" show (Just 42) -- "42"
catMaybes [Just 1, Nothing, Just 3] -- [1,3]
mapMaybe (`safeDiv` 2) [4, 6] -- [2,3]
-- Chaining Maybes (Monad, Module 19)
calc :: Int -> Int -> Int -> Maybe Int
calc a b c = do
x <- safeDiv a b
y <- safeDiv x c
pure (x + y)13.2 Either: Errors with Information
data Either a b = Left a | Right b -- by convention, Left = error, Right = success
data ValidationError = EmptyName | InvalidAge Int deriving Show
validateName :: String -> Either ValidationError String
validateName "" = Left EmptyName
validateName n = Right n
validateAge :: Int -> Either ValidationError Int
validateAge a
| a < 0 || a > 150 = Left (InvalidAge a)
| otherwise = Right a
data User = User String Int deriving Show
mkUser :: String -> Int -> Either ValidationError User
mkUser n a = User <$> validateName n <*> validateAge a
-- mkUser "Ana" 30 == Right (User "Ana" 30)
-- mkUser "" 30 == Left EmptyName
import Data.Either (either, lefts, rights, partitionEithers)
either show (\(User n _) -> n) (mkUser "Bo" 200) -- "InvalidAge 200"
partitionEithers [Left 'a', Right 1, Right 2] -- ("a",[1,2])13.3 Totality
Partial functions (head, tail, !!, fromJust, read, div by zero, incomplete patterns) can crash at run time. Prefer total alternatives: pattern matching, Maybe/Either, Data.List.NonEmpty, readMaybe, uncons.
GHC 9.8+ emits -Wx-partial warnings for uses of head/tail.
import Data.List.NonEmpty (NonEmpty (..))
import qualified Data.List.NonEmpty as NE
safeMax :: NonEmpty Int -> Int
safeMax = maximum -- can't fail: the list is guaranteed non-empty
-- safeMax (3 :| [1, 4]) == 4
-- NE.nonEmpty [] == Nothing