What do you mean when you call a language ‘lazy’?
Normally when you call a function, even in a call by value or strict functional programming language, you would evaluate the argument, and then you’d call the function. For example, once you called f on 3+4, your first step would be to evaluate 3+4 to make 7, then you’d call f and say you’re passing it 7.
In a lazy language, you don’t evaluate the 3+4 because f might ignore it, in which case all that work computing 3+4 was wasted. In a lazy language, you evaluate expressions only when their value is actually required, not when you call a function - it’s call by need. A lazy person waits until their manager says ‘I really need that report now’, whereas an eager will have it in their draw all done, but maybe their manager will never ask for it.
Lazy evaluation is about postponing tasks until you really have to do them. And that’s the thing that distinguishes Haskell from ML, or Scheme for example.
If you’re in a lazy language, it’s much more difficult to predict the order of evaluation. Will this thing be evaluated at all, and if so, when, is a tricky question to answer. So that makes it much more difficult to do input/output. Basically, in a functional language, you shouldn’t be doing input/output in an expression because input/output is a side effect.
In ML or Scheme, they say, ‘oh well, we’re functional most of the time, but for input/output we’ll be non-functional and we’ll let you do side effects and things that are allegedly functions.’ They’re not really functions however, as they have side effects. So you can call f and you can print something, or launch the missiles. In Haskell, if you call f, you can’t launch the missiles as it’s a function and it doesn’t have any side effects.
In theory, lazy evaluation means that you can’t take the ML or Scheme route of just saying ‘oh well, we’ll just allow you to do input/output side effects’, as you don’t know what order they’ll happen in. You wouldn’t know if you armed the missiles before launching them, or launched them before arming them.
I’m definitely very happy with using the lazy approach, as that’s what made Haskell what it is and kept it pure.
Because Haskell is lazy it meant that we were much more consistent about keeping the language pure. You could have a pure, strict, call by value language, but no one has managed to do that because the moment you have a strict call by value language, the temptation to add impurities (side effects) is overwhelming. So “laziness kept us pure” is the slogan!
Do you know of any other pure languages?
Miranda, designed by David Turner, which has a whole bunch of predecessor languages, several designed by David Turner - they’re all pure. Various subsets of Lisp are pure. But none widely used… oh, and Clean is pure(!). But for purely functional programming Haskell must be the brand leader.
Do you think that lazy languages have lots of advantages over non-lazy languages?
I think probably on balance yes, as laziness has lots of advantages. But it has some disadvantages too, so I think the case is a bit more nuanced there [than in the case of purity].
A lazy language has ways of stating ‘use call by value here’, and even if you were to say ‘oh, the language should be call by value strict’ (the opposite of lazy), you’d want ways to achieve laziness anyway. Any successor language [to Haskell] will have support for both strict and lazy functions. So the question then is: what’s the default, and how easy is it to get to these things? How do you mix them together? So it isn’t kind of a completely either/or situation any more. But on balance yes, I’m definitely very happy with using the lazy approach, as that’s what made Haskell what it is and kept it pure.
You sound very proud of Haskell’s purity.
That’s the thing. That’s what makes Haskell different. That’s what it’s about.