Scala foreach strange behaviour

2020-01-23 17:36发布

问题:

I want to iterate over a list of values using a beautiful one-liner in Scala.

For example, this one works well:

scala> val x = List(1,2,3,4)
x: List[Int] = List(1, 2, 3, 4)

scala> x foreach println
1
2
3
4

But if I use the placeholder _, it gives me an error:

scala> x foreach println(_ + 1)
<console>:6: error: missing parameter type for expanded function ((x$1) =>x$1.$plus(1))
       x foreach println(_ + 1)
                         ^

Why is that? Can't compiler infer type here?

回答1:

This:

x foreach println(_ + 1)

is equivalent to this:

x.foreach(println(x$1 => x$1 + 1))

There's no indication as to what might be the type of x$1, and, to be honest, it doesn't make any sense to print a function.

You obviously (to me) meant to print x$0 + 1, where x$0 would the the parameter passed by foreach, instead. But, let's consider this... foreach takes, as a parameter, a Function1[T, Unit], where T is the type parameter of the list. What you are passing to foreach instead is println(_ + 1), which is an expression that returns Unit.

If you wrote, instead x foreach println, you'd be passing a completely different thing. You'd be passing the function(*) println, which takes Any and returns Unit, fitting, therefore, the requirements of foreach.

This gets slightly confused because of the rules of expansion of _. It expands to the innermost expression delimiter (parenthesis or curly braces), except if they are in place of a parameter, in which case it means a different thing: partial function application.

To explain this better, look at these examples:

def f(a: Int, b: Int, c: Int) = a + b + c
val g: Int => Int = f(_, 2, 3) // Partial function application
g(1)

Here, we applies the second and third arguments to f, and returned a function requiring just the remaining argument. Note that it only worked as is because I indicated the type of g, otherwise I'd have to indicate the type of the argument I was not applying. Let's continue:

val h: Int => Int = _ + 1 // Anonymous function, expands to (x$1: Int => x$1 + 1)
val i: Int => Int = (_ + 1) // Same thing, because the parenthesis are dropped here
val j: Int => Int = 1 + (_ + 1) // doesn't work, because it expands to 1 + (x$1 => x$1 + 1), so it misses the type of `x$1`
val k: Int => Int = 1 + ((_: Int) + 1) // doesn't work, because it expands to 1 + (x$1: Int => x$1 + 1), so you are adding a function to an `Int`, but this operation doesn't exist

Let discuss k in more detail, because this is a very important point. Recall that g is a function Int => Int, right? So, if I were to type 1 + g, would that make any sense? That's what was done in k.

What confuses people is that what they really wanted was:

val j: Int => Int = x$1 => 1 + (x$1 + 1)

In other words, they want the x$1 replacing _ to jump to outside the parenthesis, and to the proper place. The problem here is that, while it may seem obvious to them what the proper place is, it is not obvious to the compiler. Consider this example, for instance:

def findKeywords(keywords: List[String], sentence: List[String]) = sentence.filter(keywords contains _.map(_.toLowerCase))

Now, if we were to expand this to outside the parenthesis, we would get this:

def findKeywords(keywords: List[String], sentence: List[String]) = (x$1, x$2) => sentence.filter(keywords contains x$1.map(x$2.toLowerCase))

Which is definitely not what we want. In fact, if the _ did not get bounded by the innermost expression delimiter, one could never use _ with nested map, flatMap, filter and foreach.

Now, back to the confusion between anonymous function and partial application, look here:

List(1,2,3,4) foreach println(_) // doesn't work
List(1,2,3,4) foreach (println(_)) // works
List(1,2,3,4) foreach (println(_ + 1)) // doesn't work

The first line doesn't work because of how operation notation works. Scala just sees that println returns Unit, which is not what foreachexpects.

The second line works because the parenthesis let Scala evaluate println(_) as a whole. It is a partial function application, so it returns Any => Unit, which is acceptable.

The third line doesn't work because _ + 1 is anonymous function, which you are passing as a parameter to println. You are not making println part of an anonymous function, which is what you wanted.

Finally, what few people expect:

List(1,2,3,4) foreach (Console println _ + 1)

This works. Why it does is left as an exercise to the reader. :-)

(*) Actually, println is a method. When you write x foreach println, you are not passing a method, because methods can't be passed. Instead, Scala creates a closure and passes it. It expands like this:

x.foreach(new Function1[Any,Unit] { def apply(x$1: Any): Unit = Console.println(x$1) })


回答2:

The underscore is a bit tricky. According to the spec, the phrase:

_ + 1

is equivalent to

x => x + 1

Trying

x foreach println (y => y + 1)

yields:

<console>:6: error: missing parameter type
           x foreach println (y => y + 1)

If you add some types in:

x foreach( println((y:Int) => y + 1))
<console>:6: error: type mismatch;
 found   : Unit
 required: (Int) => Unit
           x foreach( println((y:Int) => y + 1))

The problem is that you are passing an anonymous function to println and it's not able to deal with it. What you really want to do (if you are trying to print the successor to each item in the list) is:

x map (_+1) foreach println


回答3:

scala> for(x <- List(1,2,3,4)) println(x + 1)
2
3
4
5


回答4:

There is a strange limitation in Scala for the nesting depth of expressions with underscore. It's well seen on the following example:

 scala> List(1) map(1+_)
 res3: List[Int] = List(2)

 scala> Some(1) map (1+(1+_))
 <console>:5: error: missing parameter type for expanded function ((x$1) => 1.+(x$1))
        Some(1) map (1+(1+_))
                     ^

Looks like a bug for me.



回答5:

Welcome to Scala version 2.8.0.Beta1-prerelease (Java HotSpot(TM) Client VM, Java 1.6.0_17).
Type in expressions to have them evaluated.
Type :help for more information.

scala> val l1 = List(1, 2, 3)
l1: List[Int] = List(1, 2, 3)

scala>

scala> l1.foreach(println(_))
1
2
3


标签: scala