Simon Peyton Jones
259
So that’s all about why laziness is a good thing. It’s also very helpful in a very
local level in your program. You tend to find Haskell programmers will
write down a function definition with some local definitions. So they’ll say “f
of x equals blah, blah, blah where . . .” And in the
where clause they write
down a bunch of definitions and of these definitions, not all are needed in all
cases. But you just write them down anyway. The ones that are needed will
get evaluated; the ones that aren’t needed won’t. So you don’t have to
think, “Oh, goodness, all of these sub expressions are going to be evaluated
but I can’t evaluate that because that would crash because of a divide by
zero so I have to move the definition into the right branch of the
conditional.”
There’s none of that. You tend to just write down auxiliary definitions that
might be needed and the ones that are needed will be evaluated. So that’s a
kind of programming convenience thing. It’s a very, very convenient
mechanism.
But getting back to the big picture, if you have a lazy evaluator, it’s harder to
predict exactly when an expression is going to be evaluated. So that means
if you want to print something on the screen, every call-by-value language,
where the order of evaluation is completely explicit, does that by having an
impure “function”—I’m putting quotes around it because it now isn’t a
function at all—with type something like string to unit. You call this function
and as a side effect it puts something on the screen. That’s what happens in
Lisp; it also happens in ML. It happens in essentially every call-by-value
language.
Now in a pure language, if you have a function from string to unit you would
never need to call it because you know that it just gives the answer unit.
That’s all a function can do, is give you the answer. And you know what the
answer is. But of course if it has side effects, it’s very important that you do
call it. In a lazy language the trouble is if you say, “
f applied to print
"hello"
,” then whether f evaluates its first argument is not apparent to the
caller of the function. It’s something to do with the innards of the function.
And if you pass it two arguments,
f of print "hello" and print "goodbye",
then you might print either or both in either order or neither. So somehow,
with lazy evaluation, doing input/output by side effect just isn’t feasible. You
can’t write sensible, reliable, predictable programs that way. So, we had to
put up with that. It was a bit embarrassing really because you couldn’t really