Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala's lazy arguments: How do they work?

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?

like image 988
python dude Avatar asked Mar 21 '12 16:03

python dude


People also ask

What is lazy computation in Scala?

Lazy evaluation or call-by-need is a evaluation strategy where an expression isn't evaluated until its first use i.e to postpone the evaluation till its demanded.

Is Scala lazy?

Scala provides a nice language feature called lazy val that defers the initialization of a variable. The lazy initialization pattern is common in Java programs. Though it seems tempting, the concrete implementation of lazy val has some subtle issues.

What is call by name in Scala?

In Scala when arguments pass through call-by-value function it compute the passed-in expression's or arguments value once before calling the function . But a call-by-Name function in Scala calls the expression and recompute the passed-in expression's value every time it get accessed inside the function.


2 Answers

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.

like image 153
Rex Kerr Avatar answered Oct 02 '22 17:10

Rex Kerr


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
like image 36
Frank Avatar answered Sep 29 '22 17:09

Frank