为了账号安全,请及时绑定邮箱和手机立即绑定

Scala foreach奇怪的行为

Scala foreach奇怪的行为

LEATH 2019-11-30 13:47:33
我想在Scala中使用漂亮的单行代码遍历值列表。例如,这很好用:scala> val x = List(1,2,3,4)x: List[Int] = List(1, 2, 3, 4)scala> x foreach println1234但是,如果我使用占位符_,则会给我一个错误:scala> x foreach println(_ + 1)<console>:6: error: missing parameter type for expanded function ((x$1) =>x$1.$plus(1))       x foreach println(_ + 1)                         ^
查看完整描述

3 回答

?
慕雪6442864

TA贡献1812条经验 获得超5个赞

这个:


x foreach println(_ + 1)

等效于此:


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

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


您(对我而言)显然打算打印x$0 + 1,而x$0参数传递到的位置foreach。但是,让我们考虑一下...... foreach以a作为参数Function1[T, Unit],其中T是列表的类型参数。foreach相反println(_ + 1),您传递给的是返回的表达式Unit。


如果您写了,相反x foreach println,您将传递完全不同的东西。您将传递function(*)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))

这绝对不是我们想要的。事实上,如果_没有得到由最里面的表达式分隔符为界,一个永远无法使用_嵌套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

由于操作符号的工作方式,第一行不起作用。Scala只是看到printlnreturn Unit,这不是foreach期望的。


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


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


最后,很少有人期望:


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

这可行。为什么这样做留给读者练习。:-)


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


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


查看完整回答
反对 回复 2019-11-30
?
拉风的咖菲猫

TA贡献1995条经验 获得超2个赞

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


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


查看完整回答
反对 回复 2019-11-30
?
喵喵时光机

TA贡献1846条经验 获得超7个赞

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


查看完整回答
反对 回复 2019-11-30
  • 3 回答
  • 0 关注
  • 1043 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信