Scala提取器未应用地调用了两次
我刚刚发现我的提取器中的unapply由于某种原因被调用了两次。 有人知道为什么,以及如何避免吗Scala提取器未应用地调用了两次,scala,case,collect,extractor,Scala,Case,Collect,Extractor,我刚刚发现我的提取器中的unapply由于某种原因被调用了两次。 有人知道为什么,以及如何避免吗 val data = List("a","b","c","d","e") object Uap { def unapply( s:String ) = { println("S: "+s) Some(s+"!") } } println( data.collect{ case Uap(x) => x } ) 这将产生以下输出: S: a
val data = List("a","b","c","d","e")
object Uap {
def unapply( s:String ) = {
println("S: "+s)
Some(s+"!")
}
}
println( data.collect{ case Uap(x) => x } )
这将产生以下输出:
S: a
S: a
S: b
S: b
S: c
S: c
S: d
S: d
S: e
S: e
List(a!, b!, c!, d!, e!)
最终的结果很好,但在我的实际程序中,unapply是非常重要的,所以我当然不想调用它两次 您可以改为使用
map
:
scala> println( data.map{ case Uap(x) => x } )
S: a
S: b
S: c
S: d
S: e
List(a!, b!, c!, d!, e!)
不知道它为什么工作,为什么。collect将
部分函数作为输入PartialFunction
定义了两个关键成员:isDefinedAt
和apply
。当collect运行函数时,它会运行一次提取器,以确定函数是否已在某个特定输入中定义,如果已定义,则作为apply
的一部分再次运行提取器以提取值
如果有一种简单的方法可以正确实现isDefinedAt,那么您可以通过显式实现自己的PartialFunction来实现它,而不是使用case语法。或者您可以对集合执行筛选
,然后使用total函数对集合进行映射
(这基本上就是collect通过调用isDefinedAt
,然后应用
所做的事情)
另一种选择是将部分功能提升为总功能PartialFunction
定义了lift
,它将PartialFunction[a,B]
转换为a=>选项[B]
。您可以使用这个提升的函数(称之为fun
)来执行:data.map(fun).collect{case Some(x)=>x}
实际上,这在2.11中作为一个性能缺陷得到了解决:
$ skala
Welcome to Scala version 2.11.0-20130423-194141-5ec9dbd6a9 (Java HotSpot(TM) 64-Bit Server VM, Java 1.7.0_06).
Type in expressions to have them evaluated.
Type :help for more information.
scala> val data = List("a","b","c","d","e")
data: List[String] = List(a, b, c, d, e)
scala>
scala> object Uap {
| def unapply( s:String ) = {
| println("S: "+s)
| Some(s+"!")
| }
| }
defined object Uap
scala>
scala> println( data.collect{ case Uap(x) => x } )
S: a
S: b
S: c
S: d
S: e
List(a!, b!, c!, d!, e!)
请参阅上的效率说明
这是2.10的一个版本,可以通过扩展轻松解决此问题:
object Test extends App {
import scala.collection.TraversableLike
import scala.collection.generic.CanBuildFrom
import scala.collection.immutable.StringLike
implicit class Collector[A, Repr, C <: TraversableLike[A, Repr]](val c: C) extends AnyVal {
def collecting[B, That](pf: PartialFunction[A, B])(implicit bf: CanBuildFrom[Repr, B, That]): That = {
val b = bf(c.repr)
c.foreach(pf.runWith(b += _))
b.result
}
}
val data = List("a","b","c","d","e")
object Uap {
def unapply( s:String ) = {
println("S: "+s)
s match {
case "foo" => None
case _ => Some(s+"!")
}
}
}
val c = Collector[String, List[String], List[String]](data)
Console println c.collecting { case Uap(x) => x }
}
请注意,此版本的Uap是部分的:
scala> val data = List("a","b","c","d","e", "foo")
data: List[String] = List(a, b, c, d, e, foo)
scala> data.map{ case Uap(x) => x }
S: a
S: b
S: c
S: d
S: e
S: foo
scala.MatchError: foo (of class java.lang.String)
我认为如果用例是PF,那么代码应该是部分的。添加到@stew answer中,collect
实现为:
def collect[B, That](pf: PartialFunction[A, B])(implicit bf: CanBuildFrom[Repr, B, That]): That = {
val b = bf(repr)
for (x <- this) if (pf.isDefinedAt(x)) b += pf(x)
b.result
}
如您所见,它再次在此处调用。这解释了为什么它会打印两次,即一次检查它是否已定义,然后在`pf(x)中调用它时打印下一次
@索姆·斯奈特是对的。从Scala 2.11开始,TraversableLike
中的collect函数更改为:
def collect[B, That](pf: PartialFunction[A, B])(implicit bf: CanBuildFrom[Repr, B, That]): That = {
val b = bf(repr)
foreach(pf.runWith(b += _))
b.result
}
它只打印一次的原因是,它在内部调用applyOrElse
,检查它是否已定义。如果是,则在那里应用该功能(在上述情况下(b+=)
)。因此它只能打印一次。我想我不能。在我的实际应用程序中,我实际上是在unapply中执行选择逻辑,所以它并不总是返回一些(某物)。Map总是需要一对一的结果。因为OP模式匹配不是局部的,我认为否决投票是不公平的。用例很重要。另一个选项是缓存分部函数对象中的最后一个值。@Greg这是正确的,但有点老派,因为它是一个在2.11中不正确的实现细节,在2.10中有一个稍微正常的修复。
....
final def isDefinedAt(x1: String): Boolean = ((x1.asInstanceOf[String]:String): String @unchecked) match {
case check.this.Uap.unapply(<unapply-selector>) <unapply> ((x @ _)) => true
case (defaultCase$ @ _) => false
}
def collect[B, That](pf: PartialFunction[A, B])(implicit bf: CanBuildFrom[Repr, B, That]): That = {
val b = bf(repr)
foreach(pf.runWith(b += _))
b.result
}