Scala foreach奇怪的行为

时间:2010-01-31 21:15:59

标签: scala

我想在Scala中使用漂亮的单行迭代迭代值列表。

例如,这个效果很好:

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

scala> x foreach println
1
2
3
4

但如果我使用占位符_,则会给我一个错误:

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

为什么?这里不能编译推断类型吗?

5 个答案:

答案 0 :(得分:69)

此:

x foreach println(_ + 1)

相当于:

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

没有迹象表明x$1的类型可能是什么,而且说实话,打印函数没有任何意义。

你显然(对我而言)打算打印x$0 + 1,而x$0将通过foreach传递参数。但是,让我们考虑一下...... foreach作为参数获取Function1[T, Unit],其中T是列表的类型参数。您传递给foreach的内容是println(_ + 1),这是一个返回Unit的表达式。

如果你写了,而不是x foreach println,你会传递一个完全不同的东西。您将传递函数(*)println,该函数需要Any并返回Unit,因此符合foreach的要求。

由于_的扩展规则,这有点混乱。它扩展到最里面的表达式分隔符(括号或花括号),除非它们代替参数,在这种情况下它意味着不同的东西:部分函数应用程序。

为了更好地解释这一点,请查看以下示例:

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

这里,我们将第二个和第三个参数应用于f,并返回一个只需要剩余参数的函数。请注意,它只是按原样运行,因为我指出了g的类型,否则我必须指出我没有应用的参数的类型。让我们继续:

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

让我们更详细地讨论k,因为这是非常重要的一点。回想一下,g是一个函数Int => Int,对吗?所以,如果我输入1 + g,那会有意义吗?这就是在k中所做的。

令人困惑的是他们真正想要的是:

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

换句话说,他们希望x$1替换_跳到外面括号,并跳到正确的位置。这里的问题是,虽然它们看起来很明显适当的地方,但编译器并不明显。考虑这个例子,例如:

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

现在,如果我们将它扩展到括号​​之外,我们就会得到:

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

这绝对不是我们想要的。 事实上,如果_没有受到最里面的表达式分隔符的限制,则永远不能将_与嵌套mapflatMap,{{1}一起使用}和filter

现在,回到匿名函数和部分应用程序之间的混淆,请看这里:

foreach

由于操作符号的工作原理,第一行不起作用。 Scala只看到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 返回println,这不是Unit所期望的。

第二行有效,因为括号允许Scala将foreach作为一个整体进行评估。它是一个部分函数应用程序,因此返回println(_),这是可以接受的。

第三行不起作用,因为Any => Unit是匿名函数,您将其作为参数传递给_ + 1。你使println成为匿名函数的一部分,这就是你想要的。

最后,很少有人期待:

println

这很有效。它为什么会留给读者练习。 : - )

(*)实际上,List(1,2,3,4) foreach (Console println _ + 1) 是一种方法。当您编写println时,您没有传递方法,因为无法传递方法。相反,Scala创建一个闭包并传递它。它扩展如下:

x foreach println

答案 1 :(得分:13)

下划线有点棘手。根据规范,短语:

_ + 1

相当于

x => x + 1

尝试

x foreach println (y => y + 1)

的产率:

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

如果您添加了一些类型:

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

问题在于您将匿名函数传递给println并且无法处理它。你真正想做的事情(如果你试图打印列表中每个项目的后继者)是:

x map (_+1) foreach println

答案 2 :(得分:5)

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

答案 3 :(得分:-3)

Scala对带有下划线的表达式的嵌套深度有一个奇怪的限制。在以下示例中可以看到它:

 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+_))
                     ^

对我来说看起来像个错误。

答案 4 :(得分:-3)

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