I don't know why Scala doesn't haveTurns out it does “proper” lazy evaluation – likely it's just not so simple to implement, especially when you want the language to interact smoothly with the JVM.
Call-by-name is (as you've observed) not equivalent to lazy evaluation, but to replacing an argument of type a
with an argument of type () -> a
. Such a function contains the same amount of information as a plain a
value (the types are isomorphic), but to actually get at that value you always need to apply the function to the ()
dummy argument. When you evaluate the function twice, you'll get twice the same result, but it must each time be calculated anew (since automatically memoising functions is not feasible).
Lazy evaluation is equivalent to replacing an argument of type a
with an argument of a type that behaves like the following OO class:
class Lazy<A> {
function<A()> computer;
option<A> containedValue;
public:
Lazy(function<A()> computer):
computer = computer
, containerValue = Nothing
{}
A operator()() {
if isNothing(containedValue) {
containedValue = Just(computer());
}
return fromJust(containedValue);
}
}
This is essentially just a memoisation-wrapper around the specific call-by-name–function type. What's not so nice is that this wrapper relies in a fundamental way on side-effects: when the lazy value is first evaluated, you must mutate the containedValue
to represent the fact that the value is now known. Haskell has this mechanism hard-baked at the heart of its runtime, well-tested for thread-safety etc.. But in a language that tries to use an imperative VM as openly as possible, it would probably cause massive headaches if these spurious mutations were interleaved with the explicit side-effects. Especially, because the really interesting applications of lazyness don't just have a single function argument lazy (that wouldn't buy you much) but scatter lazy values all through the spine of a deep data structure. In the end, it's not just one delay-function that you evaluate later than entering the lazy function, it's a whole torrent of nested calls to such functions (indeed, possibly infinitely many!) as the lazy data structure is consumed.
So, Scala avoids the dangers of this by not making anything lazy by default, though as Alec says it does offer a lazy
keyword that basically adds a memoised-function wrapper like the above to a value.