Friday, March 27, 2015

Haskell differentiates itself from most other functional languages by
letting you reason mathematically about programs with side effects. This
post begins with a pure Haskell example that obeys algebraic equations
and then generalizes the example to impure code that still obeys the
same equations.

Algebra

In school, you probably learned algebraic rules like this one:

f * (xs + ys) = (f * xs) + (f * ys)

Now let's make the following substitutions:

Replace the mathematical multiplication with Haskell's
map function

Replace the mathematical addition with Haskell's ++
operator

These two substitutions produce the following Haskell equation:

map f (xs ++ ys) = (map f xs) ++ (map f ys)

In other words, if you concatenate the list xs with the
list ys and then map a function named
f over the combined list, the result is indistinguishable
from mapping f over each list individually and
then concatenating them.

Evaluation order

However, the above equation does not hold in most other languages.
These other languages use function evaluation to trigger side effects,
and therefore if you change the order of evaluation you change the order
of side effects.

One line #1, the two lists are evaluated first, printing
"!" and "?", followed by
evaluating the function f on all four elements, printing
"*" four times. On line #2, we call
f on each element of xs before beginning to
evaluate ys. Since evaluation order matters in Scala we get
two different programs which print the punctuation characters in
different order.

The solution

Haskell, on the other hand, strictly separates evaluation order from
side effect order using a two-phase system. In the first phase you
evaluate your program to build an abstract syntax tree of side effects.
In the second phase the Haskell runtime executes the tree by
interpreting it for its side effects. This phase distinction ensures
that algebraic laws continue to behave even in the presence of side
effects.

To illustrate this, we'll generalize our original Haskell code to
interleave side effects with list elements and show that it still obeys
the same algebraic properties as before. The only difference from before
is that we will:

Generalize pure lists to their impure analog, ListT

Generalize functions to impure functions that wrap side effects with
lift

Generalize (++) (list concatenation) to
(<|>) (ListT concatenation)

Generalize map to (=<<), which
streams elements through an impure function

You can read this as saying: if we concatenate xs and
ys and then stream their values through the impure function
f, the behavior is indistinguishable from streaming each
individual list through f first and then concatenating
them.

Let's test this equation out with some sample definitions for
xs, ys, and f that mirror their
Scala analogs:

The resulting punctuation order is identical. Many people mistakenly
believe that Haskell's mathematical elegance breaks down when confronted
with side effects, but nothing could be further from the truth.

Conclusion

Haskell preserves algebraic equations even in the presence of side
effects, which simplifies reasoning about impure code. Haskell separates
evaluation order from side effect order so that you spend less time
reasoning about evaluation order and more time reasoning about your
program logic.