Errors in Haskell

In Haskell, error handling is typically done using the Either type or custom error types. We’ll demonstrate both approaches in this example.

import Control.Monad (forM_)
import Data.Either (isLeft)

-- By convention, we return Either Error Result,
-- where Error is on the left and Result is on the right.
f :: Int -> Either String Int
f arg
    | arg == 42 = Left "can't work with 42"
    | otherwise = Right (arg + 3)

-- Custom error type for tea-making errors
data TeaError = OutOfTea | NoPower
    deriving (Show, Eq)

-- Sentinel errors
errOutOfTea :: TeaError
errOutOfTea = OutOfTea

errPower :: TeaError
errPower = NoPower

-- Function that may produce a TeaError
makeTea :: Int -> Either TeaError ()
makeTea arg
    | arg == 2 = Left errOutOfTea
    | arg == 4 = Left errPower
    | otherwise = Right ()

main :: IO ()
main = do
    -- Testing the f function
    forM_ [7, 42] $ \i -> do
        case f i of
            Left err -> putStrLn $ "f failed: " ++ err
            Right result -> putStrLn $ "f worked: " ++ show result

    -- Testing the makeTea function
    forM_ [0..4] $ \i -> do
        case makeTea i of
            Left err -> case err of
                OutOfTea -> putStrLn "We should buy new tea!"
                NoPower -> putStrLn "Now it is dark."
            Right _ -> putStrLn "Tea is ready!"

In this Haskell version:

  1. We define f to return Either String Int. This is similar to returning (int, error) in Go, but uses Haskell’s Either type.

  2. For the tea-making example, we define a custom TeaError type with two constructors: OutOfTea and NoPower. This is similar to using sentinel errors in Go.

  3. The makeTea function returns Either TeaError (). The () (unit type) is used when we only care about whether an error occurred, not about returning a value.

  4. In the main function, we use pattern matching with case expressions to handle errors. This is similar to checking for nil errors in Go.

  5. We use forM_ (a variant of for that discards its result) to iterate over lists, similar to the for loops in the Go example.

  6. Error checking in Haskell is explicit through the use of Either, similar to Go’s approach of returning errors as values.

To run this program, save it as Errors.hs and use:

$ ghc Errors.hs
$ ./Errors
f worked: 10
f failed: can't work with 42
Tea is ready!
Tea is ready!
We should buy new tea!
Tea is ready!
Now it is dark.

This Haskell code demonstrates error handling techniques that are idiomatic to Haskell while maintaining the spirit of explicit error handling from the original example.