Puzzler 35

A Listful of Dollars

A convenient feature that allows Scala to cater to different coding styles is that you can often substitute curly braces for parentheses:

  scala> (1 to 3).foreach(r => 
           print("%.5f ".format(math.Pi * r * r)))
  3.14159 12.56637 28.27433
  
scala> (1 to 3) foreach { r =>           print("%.5f ".format(math.Pi * r * r)) } 3.14159 12.56637 28.27433

Scala also provides type aliases, which allows you to give more convenient names to nontrivial types and domain-specific names to general types:

  // letters -> terms and the pages on which they appear
  type BookIndex = Map[Char, Map[String, Seq[Int]]]
  
type Fahrenheit = Int type Celsius = Int // compare with 'def toFahrenheit(celsius: Int): Int' def toFahrenheit(c: Celsius): Fahrenheit

The following program uses both of these features. What does it do?

  type Dollar = Int
  final val DollarDollar = 1
  val x: List[Dollar] = List(123)
  
println(x map { x: Int => Dollar }) println(x.map(x: Int => Dollar))

Possibilities

  1. Prints:
      List(1, 2, 3)
      List(1, 2, 3)
    
  2. Prints:
      List(1, 1, 1)
      List(1, 1, 1)
    
  3. The first println statement prints:
      List(1, 1, 1)
    

    and the second one throws an exception.

  4. The first println statement prints:
      List(1, 2, 3)
    

    and the second one fails to compile.

Explanation

You may wonder whether both statements compile, but if they do, surely it makes no difference whether you use curly braces or parentheses here. Actually, it does—the correct answer is number 3:

  scala> println(x map { x: Int => Dollar })
  List(1, 1, 1)
  
scala> println(x.map(x: Int => Dollar)) java.lang.IndexOutOfBoundsException: 3   ...

The key point is that an anonymous function can be parsed differently if it is passed in a block expression,[1] as opposed to being passed directly.[2] To understand how this results in the observed behavior, consider two different ways of interpreting the function expression:

  x: Int => Dollar

One way to see this is as a function that takes an Int and returns the value Dollar, i.e., the constant value 1. In other words, you might conclude that x is a parameter:

  (x: Int) => Dollar

Although it is never incorrect to place parentheses around parameter lists for anonymous functions, they can be omitted in certain cases. Specifically, parentheses are not required if a function takes one parameter and no type is given, as in x => Dollar. If a type is specified for the parameter, you can omit the parentheses only if the anonymous function appears as a block. This is precisely what is going on in the first println statement:

  println(x map { x: Int => Dollar })

This statement is equivalent to the following, shorter, form:

  println(x map { freshName => 1 })

Not surprisingly, the result is a list of 1 values:

  scala> println(x map { x: Int => Dollar })
  List(1, 1, 1)

Note that the two occurrences of x in this expression have different meanings: the first x refers to the list, while the second one represents a parameter of the function passed to map.

In the case of the second println statement, the anonymous function is not passed as a block expression:

  println(x.map(x: Int => Dollar))

Given that the parentheses around the parameter, x: Int, are omitted and the expression x: Int => Dollar is not inside a block, you might wonder how it even compiles. The answer is that this function expression can be read in a different way, by treating the type declaration as a type ascription to the entire expression. In other words, the expression x: Int => Dollar is parsed as x: (Int => Dollar), with x a function of type Int => Dollar, and Dollar a type alias for Int.

This means that both occurrences of x in the second println statement refer to the same value, the list x. This is only possible because Scala's List is also a Function1, which maps list indices to elements in the list. List x, a List[Dollar], is therefore a function from Int to Dollar, the type expected by map. The statement thus compiles without errors.

At runtime, consequently, the second println statement uses each value in the list as an index back into itself. More specifically, it tries to map List(1, 2, 3) to List(x(1), x(2), x(3)). Because list x only has three elements, this will work until passing the last index value, 3, results in the observed IndexOutOfBoundException.

Discussion

To achieve the same result when passing the anonymous function directly as when passing a block expression, you need to enclose the parameter in parentheses:

  scala> println(x.map((x: Int) => Dollar))
  List(1, 1, 1)

If the parameter is untyped, both println statements behave the same way:

  scala> println(x map { x => Dollar })
  List(1, 1, 1)
  
scala> println(x.map(x => Dollar)) List(1, 1, 1)

While you can pass anonymous functions whose body consists of a single expression directly or as a block, a block expression is required in the following two situations:

  // multiple statements in function body
  Seq(123) map { e =>
    val i = e + 1
    i * 2
  }
  
// using case clauses Seq(123) map {    case e if e % 2 == 0 => e + 2   case e => e + 1 }
image images/moralgraphic117px.png Be aware that passing an anonymous function in curly braces actually creates a block. Anonymous functions passed in a block can be parsed differently than when passed directly.

If you are passing an anonymous, single-parameter function in parentheses, make sure you enclose the parameter in parentheses if it has a type annotation.


Footnotes for Chapter 35:

[1] Odersky, The Scala Language Specification, Section 6.23. [Ode14]

[2] Anonymous functions passed in block expressions are also discussed in Puzzler 1.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.135.201.217