Skip to content

Case Distinctions

Now that we know the absolute basics of Haskell syntax, we are ready to learn about the mechanisms that Haskell provides to write interesting functions. Calling the examples in this chapter "interesting" is still quite a stretch, but at least we will learn how to define functions that produce different results based on some conditions.

If you think about the types of structures that most programming languages offer to produce programs with non-linear control flow (programs that are more than just a sequence of steps to be executed in a rigid sequence), then there are essentially three constructs that come to mind: conditionals (if-then-else and friends), loops, and recursion. As we will see, loops are impossible in a purely functional programming language. This leaves conditionals and recursion as the tools we have at our disposal in Haskell. This chapter focuses on conditionals. A later chapter discusses recursion.

When using case distinctions in an imperative language, we say things like "If this condition is true, then do this, else do that." In functional programs, there is no do this or that, but we still need to be able to distinguish between cases. I showed you the example of the mathematical definition of Fibonacci numbers before. Mathematicians also use some form of if-statements, say "If \(x\) is even, then \(y\) is this, otherwise \(y\) is that." If we want to program functionally, we need syntax to express these types of case distinctions. Haskell gives us a number of tools to achieve this:

  • if then else. It isn't used often because the other tools often lead to cleaner code, but it exists for when you really need it.

  • The case expression, which is similar to the switch statement in C and C++ or the match statement in Rust. Rust's match statement was in fact strongly inspired by Haskell's case expression. Both are more powerful than C and C++'s switch statement.

  • Pattern matching. We can have multiple equations defining the same function. Each equation applies if the function arguments match some pattern.

  • Pattern guards. We can specify additional conditions under which some equation defining a function should apply. Even if the function arguments match the expected patterns, the equation does not apply if the condition is not satisfied, and then we try the next equation to see whether it matches.

We'll discuss each of these possibilities in turn.

To illustrate these constructs, we use a very simple problem: a function to compute the sign of an integer:

\[\textrm{sign}(x) = \begin{cases} \phantom{-}1 & \text{if } x > 0\\ \phantom{-}0 & \text{if } x = 0\\ -1 & \text{if } x < 0 \end{cases}\]