40

In the file Parsers.scala (Scala 2.9.1) from the parser combinators library I seem to have come across a lesser known Scala feature called "lazy arguments". Here's an example:

def ~ [U](q: => Parser[U]): Parser[~[T, U]] = { lazy val p = q // lazy argument
  (for(a <- this; b <- p) yield new ~(a,b)).named("~")
}

Apparently, there's something going on here with the assignment of the call-by-name argument q to the lazy val p.

So far I have not been able to work out what this does and why it's useful. Can anyone help?

python dude
  • 7,980
  • 11
  • 40
  • 53
  • 1
    Have you put any effort in trying to find out yourself? It's part of the Scala language and an internet search should reveal enough hits on `scala lazy`. – ziggystar Mar 21 '12 at 17:17
  • 1
    @ziggystar: I've already done 2-3 Google searches and couldn't find anything helpful. Lazy arguments were mentioned on some Scala feature request, but no explanation of which I could make sense was given there. – python dude Mar 21 '12 at 17:26
  • 1
    @ziggystar: The feature request is here: https://issues.scala-lang.org/browse/SI-240. Also, searching for `scala lazy` or even `scala lazy argument` doesn't seem to yield a lot of useful info because you'll mostly get results about the more basic stuff like lazy val's and call-by-name. – python dude Mar 21 '12 at 17:30
  • 2
    Looks like you got confused by that comment. It's basically just a call-by-name argument and a `lazy val` on the same line. I thought you were merely asking what a `lazy val` is. – ziggystar Mar 21 '12 at 17:44

2 Answers2

99

Call-by-name arguments are called every time you ask for them. Lazy vals are called the first time and then the value is stored. If you ask for it again, you'll get the stored value.

Thus, a pattern like

def foo(x: => Expensive) = {
  lazy val cache = x
  /* do lots of stuff with cache */
}

is the ultimate put-off-work-as-long-as-possible-and-only-do-it-once pattern. If your code path never takes you to need x at all, then it will never get evaluated. If you need it multiple times, it'll only be evaluated once and stored for future use. So you do the expensive call either zero (if possible) or one (if not) times, guaranteed.

Rex Kerr
  • 166,841
  • 26
  • 322
  • 407
  • 2
    +1, I had assumed that call-by-name was in some equal to lazy, but not at all the case! Thanks for the clarification... – virtualeyes Mar 21 '12 at 17:34
30

The wikipedia article for Scala even answers what the lazy keyword does:

Using the keyword lazy defers the initialization of a value until this value is used.

Additionally, what you have in this code sample with q : => Parser[U] is a call-by-name parameter. A parameter declared this way remains unevaluated, until you explicitly evaluate it somewhere in your method.

Here is an example from the scala REPL on how the call-by-name parameters work:

scala> def f(p: => Int, eval : Boolean) = if (eval) println(p)
f: (p: => Int, eval: Boolean)Unit

scala> f(3, true)
3

scala> f(3/0, false)

scala> f(3/0, true)
java.lang.ArithmeticException: / by zero
    at $anonfun$1.apply$mcI$sp(<console>:9)
    ...

As you can see, the 3/0 does not get evaluated at all in the second call. Combining the lazy value with a call-by-name parameter like above results in the following meaning: the parameter q is not evaluated immediately when calling the method. Instead it is assigned to the lazy value p, which is also not evaluated immediately. Only lateron, when p is used this leads to the evaluation of q. But, as p is a val the parameter q will only be evaluated once and the result is stored in p for later reuse in the loop.

You can easily see in the repl, that the multiple evaluation can happen otherwise:

scala> def g(p: => Int) = println(p + p)
g: (p: => Int)Unit

scala> def calc = { println("evaluating") ; 10 }
calc: Int

scala> g(calc)
evaluating
evaluating
20
Frank
  • 10,461
  • 2
  • 31
  • 46
  • last example is a bit confusing. It shows (in my simplification), that call-by-name means that value evaluated every time you mention that name. So, you mention "p" two times and it gives two evaluations. So, to aviod it, you should first save evaluation in local variable: ``lazy val cache = p`` - "p" mentioned only once so Bobsyouruncle. – Alexo Po. Sep 22 '21 at 20:30