Scala foreach奇怪的行为

F0R*_*0RR 41 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
Run Code Online (Sandbox Code Playgroud)

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

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

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

Dan*_*ral 69

这个:

x foreach println(_ + 1)
Run Code Online (Sandbox Code Playgroud)

相当于:

x.foreach(println(x$1 => x$1 + 1))
Run Code Online (Sandbox Code Playgroud)

没有迹象表明可能是什么类型x$1,并且说实话,打印功能没有任何意义.

你显然(对我来说)意味着打印x$0 + 1,x$0参数传递的位置foreach,而不是.但是,让我们考虑一下...... foreach作为参数,a Function1[T, Unit],T列表的类型参数.你要传递的foreachprintln(_ + 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)
Run Code Online (Sandbox Code Playgroud)

在这里,我们将第二个和第三个参数应用于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
Run Code Online (Sandbox Code Playgroud)

让我们k更详细地讨论,因为这是非常重要的一点.回想一下,这g是一个功能Int => Int吧?所以,如果我要打字1 + g,那会有意义吗?这就是我们所做的k.

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

val j: Int => Int = x$1 => 1 + (x$1 + 1)
Run Code Online (Sandbox Code Playgroud)

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

def findKeywords(keywords: List[String], sentence: List[String]) = sentence.filter(keywords contains _.map(_.toLowerCase))
Run Code Online (Sandbox Code Playgroud)

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

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

这绝对不是我们想要的.事实上,如果_没有得到由最里面的表达式分隔符为界,一个永远无法使用_嵌套map,flatMap,filterforeach.

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

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
Run Code Online (Sandbox Code Playgroud)

由于操作符号的工作原理,第一行不起作用.Scala只看到了println回报Unit,这不是foreach预期的.

第二行有效,因为括号让Scala println(_)作为一个整体进行评估.它是一个部分函数应用程序,因此返回Any => Unit,这是可以接受的.

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

最后,很少有人期待:

List(1,2,3,4) foreach (Console println _ + 1)
Run Code Online (Sandbox Code Playgroud)

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

(*)实际上,println是一种方法.在编写时x foreach println,您没有传递方法,因为无法传递方法.相反,Scala创建一个闭包并传递它.它扩展如下:

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

  • 我认为`Console println _`使用`println`方法作为运算符,其优先级低于`+`运算符.因此,这是有效的`val k:Int => Unit = Console println _ + 1`作为匿名函数.首先计算`_ + 1`然后传递给`Console.println`. (7认同)

dav*_*000 13

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

_ + 1
Run Code Online (Sandbox Code Playgroud)

相当于

x => x + 1
Run Code Online (Sandbox Code Playgroud)

x foreach println (y => y + 1)
Run Code Online (Sandbox Code Playgroud)

收益率:

<console>:6: error: missing parameter type
           x foreach println (y => y + 1)
Run Code Online (Sandbox Code Playgroud)

如果您添加一些类型:

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

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

x map (_+1) foreach println
Run Code Online (Sandbox Code Playgroud)

  • @ F0RR这不是一个bug.它是这样指定的. (3认同)

Vik*_*ang 5

scala> for(x <- List(1,2,3,4)) println(x + 1)
2
3
4
5
Run Code Online (Sandbox Code Playgroud)