June 26, 2014

By-Name Parameters in Scala

A common question on #scala is, what’s the difference between a parameter of type A and one of type => A? The short answer is:

TL;DR … A parameter of type A acts like a val (its body is evaluated once, when bound) and one of type => A acts like a def (its body is evaluated whenever it is used).

Ok. So let’s work an example by attempting to define our own if/else control structure:

// Our own if/then/else 
def when[A](test: Boolean, whenTrue: A, whenFalse: A): A = 
  test match {
    case true  => whenTrue
    case false => whenFalse
  }

scala> when(1 == 2, "foo", "bar")
res13: String = bar

scala> when(1 == 1, "foo", "bar")
res14: String = foo

// Ok so far, but...

scala> when(1 == 1, println("foo"), println("bar"))
foo
bar

scala> // hmmm

The problem here is that the arguments to when are always evaluated, even if they are never used. This means that at best we’re doing extra work, and at worst we’re triggering side-effects that should not happen.

Scala has a solution to this problem called by-name parameters. By declaring a parameter as a: => A (note that the space after the : is necessary) we are telling Scala to evaluate a only when it is used (which may be never). So let’s fix our method.

def when[A](test: Boolean, whenTrue: => A, whenFalse: => A): A = 
  test match {
    case true  => whenTrue
    case false => whenFalse
  }

// Try that again...

scala> when(1 == 1, println("foo"), println("bar"))
foo

scala> when(1 == 2, println("foo"), println("bar"))
bar

scala> // much better

So this is actually quite nice. We have defined a new control structure as a library function, which is something you can’t really do in most languages. Although if and while are built-in syntax in Scala, they don’t need to be; and unlike most other languages which define short-circuiting boolean operators in syntax, Scala defines && and || simply as methods with by-name parameters.

So is this laziness?

Not exactly. When we talk about laziness we mean that an expression is reduced by need, as is the case with lazy val in Scala. In case you’re not familiar, let’s generate a random number in three ways, first as a val:

scala> val a = { println("computing a"); util.Random.nextInt(10) }
computing a
a: Int = 5

scala> List(a, a, a, a, a)
res4: List[Int] = List(5, 5, 5, 5, 5)

So a was computed immediately, one time, as expected. What if it’s a def?

scala> def a = { println("computing a"); util.Random.nextInt(10) }
a: Int

scala> List(a, a, a, a, a)
computing a
computing a
computing a
computing a
computing a
res5: List[Int] = List(1, 6, 8, 4, 3)

Again, no surprises; a is computed each time it’s used. And what about lazy val?

scala> lazy val a = { println("computing a"); util.Random.nextInt(10) }
a: Int = <lazy>

scala> List(a, a, a, a, a)
computing a
res6: List[Int] = List(4, 4, 4, 4, 4)

So the lazy val is computed once like any other val, but not until its value is needed.

So what happens with a by-name parameter? It might not be what you expect.

scala> def five[A](a: => A): List[A] = List(a, a, a, a, a)
five: [A](a: => A)List[A]

scala> five { println("computing a"); util.Random.nextInt(10) }
computing a
computing a
computing a
computing a
computing a
res7: List[Int] = List(1, 4, 6, 1, 5)

The by-name parameter behaves like the def above, not like lazy val. So although we used by-name parameters to implement lazy behavior for our when function above, this is dependent on only referencing each parameter once.

A by-name parameter acts like a def.

Note that we can get the lazy behavior we might have expected by combining a by-name parameter with a lazy val in the function body.

scala> def five[A](a: => A): List[A] = { lazy val b = a; List(b, b, b, b, b) }
five: [A](a: => A)List[A]

scala> five { println("computing a"); util.Random.nextInt(10) }
computing a
res8: List[Int] = List(9, 9, 9, 9, 9)

Anything else?

Glad you asked. Here are a few more tidbits that might come in handy:

Done.

Hope this helps. Please let me know if you find any errors above or have suggestions for improving the explanation.