Feature request: supplement by-name parameter syntax with "lazy" keyword

#1

I can define a lazy value like so:

lazy val i = { print("evaluated!"); 42 }

If I pass that value to a method as a normal parameter then it will be immediately evaluated:

def willEvaluate(n: Int) = println("hello")
willEvaluate(i) // Prints "evaluated! hello"

I can defer evaluation by passing it as a by-name parameter

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:

def doesntCompile(lazy val n: Int) = ...
def doesntCompile(lazy n: Int) = ...
def doesntCompile(n: lazy Int) = ...

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?

2 Likes
#2

Lazy evaluates only once. Call by name evaluates each time it is needed.

I like the idea, but I think it should work like lazy and not call by name.

2 Likes
#3

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.

1 Like
#4

Actually it’s not. Memoization can be updated if the input is updated. Lazy vals are only updated once.

#5

I think it should work like lazy and not call by name.

Yes! That would be even better.

#6

Here is a request for exactly this in 2007: https://github.com/scala/bug/issues/240

#7

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.

#8

OK, I understand what you mean now.

#9

Maybe you should checkout this dotty PR: https://github.com/lampepfl/dotty/pull/6967

#10

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.)

4 Likes
#11

See the discussion on the Dotty issue here.

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.

2 Likes