For example, I have an operation fnB :: a -> Bool
that makes no sense until fnA :: Bool
returns False
. In C I may compose these two operations in one if
block:
if( fnA && fnB(a) ){ doSomething; }
and C will guarantee that fnB
will not execute until fnA
returns false.
But Haskell is lazy, and, generally, there is no guarantee what operation will execute first, until we don't use seq
, $!
, or something else to make our code strict. Generally, this is what we need to be happy. But using &&
operator, I would expect that fnB
will not be evaluated until fnA
returns its result. Does Haskell provide such a guarantee with &&
? And will Haskell evaluate fnB
even when fnA
returns False?
The function (&&)
is strict in its second argument only if its first argument is True
. It is always strict in its first argument. This strictness / laziness is what guarantees the order of evaluation.
So it behaves exactly like C. The difference is that in Haskell, (&&)
is an ordinary function. In C, this would be impossible.
But Haskell is lazy, and, generally, there are no guarantee what operation will execute first, until we don't use seq, $!, or something else to make our code strict.
This is not correct. The truth is deeper.
Crash course in strictness:
We know (&&)
is strict in its first parameter because:
⊥ && x = ⊥
Here, ⊥ is something like undefined
or an infinite loop (⊥ is pronounced "bottom"). We also know that (False &&)
is non-strict in its second argument:
False && ⊥ = False
It can't possibly evaluate its second argument, because its second argument is ⊥ which can't be evaluated. However, the function (True &&)
is strict in its second argument, because:
True && ⊥ = ⊥
So, we say that (&&)
is always strict in its first argument, and strict in its second argument only when the first argument is True
.
Order of evaluation:
For (&&)
, its strictness properties are enough to guarantee order of execution. That is not always the case. For example, (+) :: Int -> Int -> Int
is always strict in both arguments, so either argument can be evaluated first. However, you can only tell the difference by catching exceptions in the IO
monad, or if you use an unsafe
function.