Skip to content

Conditional Evaluation of Expressions

As an example for why short-circuit evaluation is useful, I gave this example in C:

if (ptr != NULL && *ptr > 10) {
    ...
} else {
    ...
}

Evaluating the expression *ptr > 10 is safe only if ptr != NULL. Clearly, there are scenarios where expressions of types other than Bool are safe to evaluate only under certain conditions. For example, we want to evaluate x / y only if y is not 0. Or we want to take the tail of a list only if the list is non-empty. Let's take the division by 0 example:

safeQuotient :: Double -> Double -> Maybe Double
safeQuotient x y | y == 0    = Nothing
                 | otherwise = Just (x / y)

There's little to improve about this definition, but for the sake of discussion, let's assume that we want to assign the result of x / y to a local variable before returning the result. Here's how we can do this:

safeQuotient :: Double -> Double -> Maybe Double
safeQuotient x y | y == 0    = Nothing
                 | otherwise = let q = x / y
                               in  Just q

Clearly, we calculate q = x / y only when it is safe to do so. However, most Haskell programmers find where blocks more readable and use let expressions only when absolutely necessary. So we would really like to write

safeQuotient :: Double -> Double -> Maybe Double
safeQuotient x y | y == 0    = Nothing
                 | otherwise = Just q
  where
    q = x / y

And it turns out we can, thanks to lazy evaluation. The variable q is visible in both branches of the definition because, remember, a where block is attached to the entire equation, not just to an individual branch. In the first branch, when y == 0, the result is Nothing, so we will never ask for the value of q. The dangerous expression x / y is never evaluated. In the second branch, we return Just q. Thus, the caller of safeQuotient may evaluate q at some point. But this is fine. We return Just q only if y is not 0, so the division x / y is safe.

In summary, lazy evaluation allows us to define expressions that we know aren't always safe to evaluate, as long as we only evaluate them when they are safe to evaluate. This can lead to more readable code—the above is a bad example—and may help to make our program more efficient.

How can this help efficiency? Assume we have a function someCostlyComputation that fails when given an empty list as argument, and assume we need the result of someCostlyComputation twice in our code, something like this:

madeUpFunction :: [Int] -> Int
madeUpFunction xs | null xs   = 0
                  | otherwise = y * y
  where
    y = someCostlyComputation xs

Without local variables, we would have to define madeUpFunction as

madeUpFunction :: [Int] -> Int
madeUpFunction xs | null xs   = 0
                  | otherwise = someCostlyComputation xs * someCostlyComputation xs

but this would evaluate the expression someCostlyComputation xs twice and thus would be more expensive. So we really need a local variable here. We could have introduced it in the otherwise branch using a let expression, as we did with safeQuotient before, but we don't have to, thanks to lazy evaluation.

A note: This type of scenario does come up in real programs, but I found it hard to come up with a small standalone example. Hence madeUpFunction and someCostlyComputation. I hope the idea is clear and your imagination sees how this might apply to real-world situations.