为什么必须在scala中的for循环中为模式匹配定义过滤器?

Ste*_*eve 18 for-loop scala map filter pattern-matching

要创建一个可以在Scala中用于理解的新类,似乎所有你需要做的就是定义一个map函数:

scala> class C[T](items: T*) {
     |   def map[U](f: (T) => U) = this.items.map(f)
     | }
defined class C

scala> for (x <- new C(1 -> 2, 3 -> 4)) yield x
res0: Seq[(Int, Int)] = ArrayBuffer((1,2), (3,4))
Run Code Online (Sandbox Code Playgroud)

但这仅适用于简单的for循环,其中左侧没有模式匹配<-.如果您尝试在那里进行模式匹配,则会收到filter未定义方法的投诉:

scala> for ((k, v) <- new C(1 -> 2, 3 -> 4)) yield k -> v
<console>:7: error: value filter is not a member of C[(Int, Int)]
       for ((k, v) <- new C(1 -> 2, 3 -> 4)) yield k -> v
Run Code Online (Sandbox Code Playgroud)

为什么需要过滤器来实现模式匹配?我原以为Scala会把上面的循环翻译成等效的map调用:

scala> new C(1 -> 2, 3 -> 4).map{case (k, v) => k -> v}
res2: Seq[(Int, Int)] = ArrayBuffer((1,2), (3,4))
Run Code Online (Sandbox Code Playgroud)

但这似乎工作正常,因此for循环必须转换为其他东西.什么是翻译成需要的filter方法?

rxg*_*rxg 18

简短的回答:根据Scala规范,您不需要为您给出的示例定义"过滤器"方法,但是有一个开放的错误,这意味着它当前是必需的.

答案很长:Scala语言规范中描述了适用于理解的desugaring算法.让我们从第6.19节"对于理解和循环"(我正在查看规范的2.9版)开始:

在第一步中,每个生成器p < - e,其中p对于e的类型不是无可辩驳的(第8.1节),由p < - e.withFilter {case p => true; case _ => false}

你问题的重点在于理解中的模式是否对于给定的表达式是"无可辩驳的".(模式是'< - '之前的位;表达式是之后的位.)如果它是"无可辩驳的",则不会添加withFilter,否则将需要它.

很好,但"无可辩驳"是什么意思?跳到规范的第8.1.14节("Irrefutable Patterns").粗略地说,如果编译器在匹配表达式时可以证明模式不会失败,那么模式是无可辩驳的,并且不会添加withFilter调用.

现在你的例子按预期工作是第8.1.14节中的第一种无可辩驳的模式,一种变量模式.因此第一个示例很容易让编译器确定withFilter不需要.

你的第二个例子可能是第三种无可辩驳的模式,一种构造函数模式.试图匹配(k,v)Tuple2[Any,Any]与a Tuple2[Int,Int](参见规范中的8.1.6和8.1.7节))成功,因为Int无可辩驳Any.因此,第二种模式也是无可辩驳的,并且(不应该)需要一种withFilter方法.

在丹尼尔的例子中,Tuple2[Any,Any]不是无可辩驳的Any,因此添加了withFilter调用.

顺便说一句,错误消息谈论了一个filter方法,但规范谈到withFilter- 它是用Scala 2.8改变的,看到这个问题和答案的血淋淋的细节.


Dan*_*ral 12

看到不同:

scala> for ((k, v) <- List(1 -> 2, 3 -> 4, 5)) yield k -> v
res22: List[(Any, Any)] = List((1,2), (3,4))

scala> List(1 -> 2, 3 -> 4, 5).map{case (k, v) => k -> v}
scala.MatchError: 5
Run Code Online (Sandbox Code Playgroud)

  • 哇,我从来没有意识到for循环会默默地丢弃不匹配的项目.这听起来像一个微妙的陷阱,我最好注意. (11认同)