斯卡拉foreach奇怪的行为
我想在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) ^
这是为什么? 编译器不能在这里推断types?
这个:
x foreach println(_ + 1)
相当于这个:
x.foreach(println(x$1 => x$1 + 1))
没有迹象表明x$1
的types可能是什么,而且说实话,打印一个函数没有任何意义。
你显然(对我来说)打印x$0 + 1
,其中x$0
将通过foreach
传递参数,而不是。 但是,让我们考虑这个… foreach
需要一个Function1[T, Unit]
作为参数,其中T
是列表的types参数。 你传递给foreach
是println(_ + 1)
,这是返回Unit
的expression式。
如果你写了,而不是x foreach println
,你会传递一个完全不同的东西。 您将传递函数(*) println
,它将Any
println
并返回Unit
,因此符合foreach
的要求。
由于_
的扩展规则,这被略微混淆了。 它扩展到最里面的expression式分隔符(括号或花括号),除非它们代替参数,在这种情况下,它意味着不同的事情:部分函数应用。
为了更好地解释这个问题,请看这些例子:
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
的types,否则我不得不指出我没有申请的论点的types。 让我们继续:
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
,对吗? 所以,如果我要input1 + g
,那会有什么意义呢? k
就是这样做的。
令人困惑的是他们真正想要的是:
val j: Int => Int = x$1 => 1 + (x$1 + 1)
换句话说,他们希望x$1
replace_
跳到括号之外 ,并且到适当的地方。 这里的问题是,虽然他们看起来很明显,但是编译器并不明显。 考虑这个例子,例如:
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))
这绝对不是我们想要的。 事实上,如果_
没有受到最内层expression式分隔符的限制,就不能使用嵌套的map
, flatMap
, filter
和foreach
。
现在,回到匿名函数和部分应用程序之间的混淆,看看这里:
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
,这不是什么foreach
期望。
第二行工作,因为括号让Scala评估println(_)
作为一个整体。 这是一个部分函数应用程序,所以它返回Any => Unit
,这是可以接受的。
第三行不起作用,因为_ + 1
是匿名函数,您将其作为parameter passing给println
。 你并没有将println
作为匿名函数的一部分,这正是你想要的。
最后,很less有人期待:
List(1,2,3,4) foreach (Console println _ + 1)
这工作。 为什么它会作为练习留给读者。 🙂
(*)实际上, println
是一种方法。 当你写x foreach println
,你没有传递一个方法,因为方法不能被传递。 相反,Scala创build一个闭包并通过它。 它像这样扩展:
x.foreach(new Function1[Any,Unit] { def apply(x$1: Any): Unit = Console.println(x$1) })
下划线有点棘手。 根据规范,这句话:
_ + 1
相当于
x => x + 1
试
x foreach println (y => y + 1)
收益率:
<console>:6: error: missing parameter type x foreach println (y => y + 1)
如果你添加一些types:
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
scala> for(x <- List(1,2,3,4)) println(x + 1) 2 3 4 5
Scala中有一个奇怪的限制,用下划线expression嵌套深度。 在下面的例子中很好看:
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+_)) ^
看起来像一个bug。
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