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