Conditionals
Haskell’s conditional constructs are expressions, not statements: every form yields a value. The principal forms are if … then … else, the case expression, and guards in function definitions. Pattern matching is integrated throughout — every conditional admits patterns alongside boolean tests. The expression-orientation has the consequence that if must have an else branch (the language has no statement form that lets the conditional yield no value), and the choice between if/then/else, case, and guards is largely stylistic. Modern code tends to favour case and guards for non-trivial discriminations and if only for simple boolean conditions.
This page covers the principal selection constructs; the Pattern matching page covers the pattern grammar in detail.
if / then / else
The simplest form:
abs :: Int -> Int
abs n = if n < 0 then negate n else n
classify :: Int -> String
classify n =
if n > 0
then "positive"
else if n < 0
then "negative"
else "zero"
The if expression has three parts:
- The condition — a
Bool-valued expression. - The then-branch — the value if the condition is
True. - The else-branch — the value if the condition is
False.
Both branches are required and must have the same type; the result type is that common type.
The cascading form if … then … else if … is admissible but rarely the conventional choice; case or guards (treated below) are typically clearer for multi-way conditionals.
Boolean-only condition
The condition must be of type Bool. Haskell does not admit C-style truthy values: 0, null, "" are not false:
if 0 then "yes" else "no" -- ERROR: 0 is not a Bool
n :: Int
if n then "yes" else "no" -- ERROR: n is Int, not Bool
if n /= 0 then "yes" else "no" -- OK
The strict typing eliminates an entire class of errors that C-family programmers learn to guard against.
Guards
A guard is a boolean condition attached to a function equation:
abs :: Int -> Int
abs n
| n < 0 = negate n
| otherwise = n
classify :: Int -> String
classify n
| n > 0 = "positive"
| n < 0 = "negative"
| otherwise = "zero"
Each guard begins with | and ends with =. The guards are evaluated top-to-bottom; the first one that evaluates to True selects the corresponding right-hand side.
otherwise is just True — a name in the standard library:
otherwise :: Bool
otherwise = True
The convention is to use otherwise for the catch-all case, which reads more naturally than True.
Guards may include multiple conditions:
classify :: Int -> Int -> String
classify x y
| x == y && x > 0 = "both positive equal"
| x == y = "equal"
| x > y = "first greater"
| otherwise = "second greater"
If no guard matches, the equation is considered to fail, and the next equation (if any) is tried. If no equation in the function matches, a runtime error is produced — Non-exhaustive patterns in function f.
case expressions
The case expression admits arbitrary pattern matching against an expression’s value:
classify :: Int -> String
classify n = case n of
0 -> "zero"
1 -> "one"
_ -> "many"
shapeName :: Shape -> String
shapeName s = case s of
Circle _ -> "circle"
Square _ -> "square"
Rectangle _ _ -> "rectangle"
The form: case <expr> of { <pattern> -> <result>; ... }. The <expr> is evaluated; the patterns are tried in order; the first matching pattern’s right-hand side is the result.
The case expression is the conventional Haskell mechanism for value-driven selection. Function-equation patterns desugar to case:
-- These are equivalent:
factorial 0 = 1
factorial n = n * factorial (n - 1)
factorial n = case n of
0 -> 1
_ -> n * factorial (n - 1)
The function-equation form is shorter; the case form is necessary when the discrimination is on something other than the function’s argument (e.g., on a derived value).
Patterns and guards together
A case arm may have guards:
classify :: Int -> String
classify n = case n of
0 -> "zero"
n
| n > 0 -> "positive"
| otherwise -> "negative"
The n pattern matches anything; the guards then refine. The form is the conventional way to combine type/constructor matching with value comparisons.
LambdaCase
The LambdaCase extension admits a shorthand for \x -> case x of …:
{-# LANGUAGE LambdaCase #-}
classify :: Int -> String
classify = \case
0 -> "zero"
n | n > 0 -> "positive"
| otherwise -> "negative"
The form is the conventional Haskell idiom for a single-argument function that immediately pattern-matches.
The MultiWayIf extension (rarely used) admits if-expressions with multiple guard arms without an explicit case:
{-# LANGUAGE MultiWayIf #-}
classify n = if
| n > 0 -> "positive"
| n < 0 -> "negative"
| otherwise -> "zero"
The form is occasionally clearer than the cascading if … then … else if … chain.
The conditional operator
Haskell does not have a dedicated ternary ?: operator. The if/then/else is itself the equivalent:
greater = if x > y then x else y
The if-expression form is conventional and reads naturally. Some libraries provide a bool function that takes the false-branch first:
import Data.Bool (bool)
bool :: a -> a -> Bool -> a
bool f t b = if b then t else f
The function admits bool x y cond as an alternative to if cond then y else x; it is mostly used for point-free style.
Pattern matching as conditional
Pattern matching is itself a discrimination mechanism. Function definitions with multiple equations are conventionally the conditional form:
factorial :: Int -> Int
factorial 0 = 1
factorial n = n * factorial (n - 1)
length :: [a] -> Int
length [] = 0
length (_:xs) = 1 + length xs
describe :: Maybe Int -> String
describe Nothing = "no value"
describe (Just n) = "value: " ++ show n
The mechanism subsumes much of what other languages express with if/switch. The full treatment is in Pattern matching.
Selection idioms
Several patterns recur in idiomatic Haskell.
Early return through pattern matching
parse :: String -> Either String Int
parse "" = Left "empty input"
parse s = case reads s of
[(n, "")] -> Right n
_ -> Left ("could not parse: " ++ s)
The pattern matching admits handling each case directly; there is no if/else cascade.
Validate-and-process via guards
process :: Int -> String -> Result
process n s
| n < 0 = errorResult "negative count"
| length s == 0 = errorResult "empty string"
| length s > 100 = errorResult "string too long"
| otherwise = computeResult n s
Guards admit discriminating on multiple conditions concisely. The conventional contemporary form for “try several things and produce the first matching error”.
Maybe-driven branching
greet :: Maybe String -> String
greet Nothing = "Hello, stranger."
greet (Just name) = "Hello, " ++ name ++ "."
Pattern matching on Maybe is the conventional Haskell idiom for “value or absence” — substantially clearer than C-family null checks.
case on a derived value
process :: User -> Action
process user = case userStatus user of
Active -> normalProcess user
Banned -> rejectProcess user
Pending -> queueProcess user
The case discriminates on the result of userStatus, not on the function’s argument directly.
Folding Maybe and Either
The standard library provides several “fold-like” functions on Maybe and Either:
maybe :: b -> (a -> b) -> Maybe a -> b
maybe d _ Nothing = d
maybe _ f (Just x) = f x
either :: (a -> c) -> (b -> c) -> Either a b -> c
either f _ (Left x) = f x
either _ g (Right x) = g x
fromMaybe :: a -> Maybe a -> a
fromMaybe d Nothing = d
fromMaybe _ (Just x) = x
The functions admit handling Maybe/Either without an explicit case:
maybe "stranger" (\n -> "Hello, " ++ n) name
fromMaybe 0 maybeCount
either (const 0) id maybeError
The pattern is the conventional Haskell idiom for the cases where the explicit case would be unnecessarily verbose.
A note on the absence of statements
Haskell does not have if as a statement (the if x then y without an else form does not exist). Every if is an expression that yields a value, so both branches are required. The implication: discriminations that don’t naturally fit into the expression form (e.g., “do this if the condition is true; otherwise do nothing”) are typically expressed as monadic when:
import Control.Monad (when, unless)
when :: Monad m => Bool -> m () -> m ()
when True action = action
when False _ = return ()
unless :: Monad m => Bool -> m () -> m ()
unless = when . not
In an IO context:
main = do
n <- readInt
when (n > 0) $ do
putStrLn "positive"
unless (n == 0) $ do
putStrLn "non-zero"
The when and unless functions are the conventional Haskell idiom for conditional effects.
For pure code, the case and if expressions are the principal conditional mechanism; for effectful code, when and unless admit the conditional-effect pattern. The choice is determined by whether the conditional should yield a value.