The case expression in Haskell

Many imperative languages have Switch case syntax: we take a variable and execute blocks of code for specific values of that variable. We might also include a catch-all block of code in case the variable has some value for which we didn’t set up a case.

But Haskell takes this concept and generalizes it: case constructs are expressions, much like if expressions and let bindings. And we can do pattern matching in addition to evaluating expressions based on specific values of a variable

Speaking of pattern matching: we already saw this when we discussed function definitions. Well, that’s actually just syntactic sugar for case expressions. The two pieces of code below do the same thing and are interchangeable:

ghci 113> let {head' :: [a] -> a;
			head' [ ] = error "No head for empty lists!";
			head' (x: _) = x}

ghci 114> head' "whassup"

ghci 115> head' ""
*** Exception: No head for empty lists!
ghci 116> let {head" :: [a] -> a;
		head" xs = case xs of {
			[ ] -> error "No head for empty lists!";
			(x: _) -> x} }

ghci 117> head00 "whassup"

ghci 118> head00 ""
*** Exception: No head for empty lists!

Thus, the syntax for case expressions is as follows

case expression of pattern -> result
					pattern -> result
					pattern -> result

The expression is matched against the patterns. The pattern matching action is what we expect: the first pattern that matches the expression is used. If we fall through the whole case expression and no suitable pattern is found, a runtime error occurs.

Note that, if we use both guards and case expressions in function definitions, the guards cannot appear inside case expressions, they have to take scope over them.

This goes well with the informal characterization of guards as presuppositions: they need to be specified before the function application is computed (i.e., the functional expression is evaluated / assigned a semantic value), hence they need to be specified before any specification of the function value.

In contrast, case expressions are just a way to specify actual function values, i.e., what should get computed assuming the guards / presuppositions are satisfied.

ghci 119> let {lessThanTwo :: (Integral a) => a -> String;
				lessThanTwo x
					| x < 2 = case x of {
					0 -> "zero";
					1 -> "one";
					x -> "negative number"}
					| otherwise = "two or more"}
ghci 120> lessThanTwo 0

ghci 121> lessThanTwo 1

ghci 122> lessThanTwo (−5)
"negative number"

ghci 123> lessThanTwo 5
"two or more"

Embedding case expressions

Whereas pattern matching on function parameters can only be done when defining functions, case expressions can be used pretty much anywhere. For instance, they are useful for pattern matching against something in the middle of an expression:

ghci 124> let {describeList :: [a] -> String;
			describeList xs = "The list is " ++ case xs of {
			[ ] -> "empty.";
			[x] -> "a singleton list.";
			xs -> "a longer list."} }
ghci 125> describeList [ ]
"The list is empty."

ghci 126> describeList [1]
"The list is a singleton list."

ghci 127> describeList [1 . . 5]
"The list is a longer list."

Alternatively, we could have used a where binding and a function definition like so:

ghci 128> let {describeList' :: [a] -> String;
			describeList0 xs = "The list is " ++ what xs
			where {
			what [ ] = "empty.";
			what [x] = "a singleton list.";
			what xs = "a longer list."} }
ghci 129> describeList0
[ ]
"The list is empty."
ghci 130> describeList' [1]
"The list is a singleton list."
ghci 131> describeList' [1 . . 5]
"The list is a longer list."

But remember that a function definition with pattern matching is just syntactic sugar for a case expression, so using a where binding and a function definition like we did above is just a roundabout way of saying what we said more concisely with a case expression the first time around.

Improving readability: case expressions vs. where bindings

In this particular situation, going for a case expression directly improves readability because the case expression appears at the end of the main function definition. Using where just adds more words without improving readability. But there are cases in which where bindings are more readable, e.g., if the case expression would have to appear in the middle of the definition of the main function, or we would have to use multiple large case expressions, etc.

About Author :

I am Pavankumar, Having 8.5 years of experience currently working in Video/Live Analytics project.

Comment / Suggestion Section
Point our Mistakes and Post Your Suggestions