def wontEvaluate(n: => Int) = println("hello")
wontEvaluate(i) // Prints "hello"
Given the similar nature of by-name parameters and lazy values it is odd that you can’t use the lazy keyword as a method of passing lazy values without evaluating them:
In fact, when one passes a lazy val: Int where a => Int is required one might actually be surprised that it does compile, as at first glance the types seem to be different. When it does compile, you may think, “What is happening? Is there an implicit conversion? Is one syntactic sugar for the other?”
I submit that allowing one to define a lazy parameter would improve Scala’s readability and useability.
Are there important distinctions between => Int and lazy val i: Int that would preempt such syntax?
This just reminds me that ‘lazy val’ is a bad name and leads to poor assumptions about how best to use it. A better name would have been ‘memoize def’.
After all, that is what it is. It behaves like a def more than a val in many critical ways (especially w.r.t. initialization and inheritance).
I frequently need to teach beginners this, telling them not to think of it as a val at all.
The lazy prefix is only applicable in contexts where val can be replaced with def… because its truly a def at heart.
Only in the case that def and val can be interchanged (no params, a member or local declaration) can ‘lazy’ be prepended to val.
There are no parameters here. Its isomorphic to memoizing a parameterless def.
One could say that Scala only supports memoization of parameterless defs, and extend that to memoizing by parameters later. But ‘lazy vals with parameters’ doesnt make sense.
Semantically it is a val in that (1) it requires storage for its contents, and (2) it doesn’t change.
Semantically it is lazy in that it isn’t computed until it’s needed.
Memoization of parameterless* immutable/pure defs is maybe semantically equivalent, but why explain a simple two-part concept with four concepts, one of which (memoization) is not exactly simple, and which doesn’t tell the entire story unless you think carefully about implementation?
(* They’re generally not actually parameterless unless they’re useless. The parameters are just passed implicitly e.g. by being part of the object upon which the def is defined. So I don’t know that this concept is simple either.)
TL;DR the main gain here would be the ability use lazy parameters (given or normal) in stable paths. However, there are open issues (null-related) about soundness of lazy vals in stable paths.
I think that actually nothing unusual happens, only the intuition is wrong. => A is a sugar for () => A (which itself is also a sugar). => A is shorter than () => A and also allows to change from call-by-value to call-by-name without changing client code.
If you have a method def method1(param1: => Int): Unit and you call it as method1(3 + compute("x", 2)) then it’s equivalent as having def method2(param1: () => Int): Unit and calling it as method2(() => 3 + compute("x", 2)). The argument body is not special and you can of course use lazy val in it. What happens then? method1(someLazyVal + 5) is eqivalent to method2(() => someLazyVal + 5) so if someLazyVal is not evaluated before method2 invocation then only invoking the passed in function evaluated that lazy val.
If you change lazy vals to ordinary vals or if you change lazy vals to defs then you will still get exactly the same behaviour in A and B variants. In fact both variants looks the same in bytecode, here’s the output of javap -p -c
Notice how both => Int and () => Int got both compiled down to Function0[Int] and how bytecodes for both variants (A with call-by-name and B with explicit Function0) are exactly the same.
TL;DR:
lazy val was not converted to Function0 when passing argument by name, it was wrapped in it just as anything else would.