- scala> var count = 0
- count: Int = 0
- // the last statement is returned as a function so count
- // is incremented only one during the creation of the function
- scala> List(1,2,3,4).map{count += 1;_ + 1}
- res9: List[Int] = List(2, 3, 4, 5)
- scala> count
- res10: Int = 1
- // now the count increment is within the function
- scala> List(1,2,3,4).map{i => count += 1;i + 1}
- res11: List[Int] = List(2, 3, 4, 5)
- scala> count
- res12: Int = 5
The previous example demonstrates a Gotcha if I ever saw one. Map expects a function so the block essentially constructs a function. The last statement being the function. The first line
count += 1
executed only once because it is part of creating the function not part of the resulting function. This is equivalent to:- scala> val x = {count += 1 ; i:Int => i +1}
- x: (Int) => Int = < function1>
- scala> List(1,2,3,4).map(x)
- res15: List[Int] = List(2, 3, 4, 5)
Beginning a block with the parameter list signals that the entire block is a function.
Rule of thumb: Functions with placeholder parameters should be a single statement.
Whoa, I had no idea...
ReplyDelete"Rule of thumb: Functions with placeholder parameters should be a single statement."
That should be _without_ placeholder params, right?
I guess that depends on what I mean by placeholder params :).
ReplyDelete{_.toString} has a placeholder param (by my definiition) and my rule is that it should always be a single statement. otherwise use explicit parameters like:
{x => x.toString}
I wonder why... is just a bug or it has some purpose?
ReplyDeleteThis is not a bug. It is an effect of consistency. As I mention in the post a block is a sequence of statements and only the last statement is used in the return. The {x => xxx} is actually a special construct of:
ReplyDeleteval x = (m:Int) => {println("hi"); m + 1}
The { x=> xxx} construct is nice when passing a function as a paramter:
method(x => {x+2})
vs
method {x=> x+2}
It is a case of reducing syntactic noise.
In summary all the choices separately make sense but when combined obviously has an unexpected side effect.
I think there is a case when the whole block will be executed multiple times, and that is when you use a "call by name" argument. E.g.
ReplyDeletescala> var count=0
count: Int = 0
scala> def g(x: =>Unit) = { for (i<-1 to 4) {x} }
g: (x: => Unit)Unit
scala> g{count+=1; println("hi")}
hi
hi
hi
hi
scala> count
res13: Int = 4
I'm just starting to learn Scala and this kind of details are perplexing. Nevertheless, I think I found a place where it makes sense:
ReplyDeletescala> List(5,6,7,8).map{var num = 0; i:Int=> {num = num+1; num.toString + "_" + i}}
res44: List[java.lang.String] = List(1_5, 2_6, 3_7, 4_8)
I'm using the part that is executed only once to do some initialization, and the rest of the block (the function) is executed once per element in the list, using the initialized values.