我刚刚发现我的提取器中的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
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是非常重要的,所以我当然不想把它叫两次!
答案 0 :(得分:6)
collect需要PartialFunction
作为输入。 PartialFunction
定义了两个关键成员:isDefinedAt
和apply
。当collect运行你的函数时,它运行你的提取器一次,以确定你的函数isDefinedAt
是否有一些特定的输入,如果是,那么它会再次作为apply
的一部分运行提取器来提取值。 / p>
如果有一种正确实现isDefinedAt的简单方法,您可以通过显式实现自己的PartialFunction来实现它,而不是使用case语法。或者你可以在集合上使用总函数filter
然后map
(这实际上是收集通过调用isDefinedAt
,然后调用apply
)
另一种选择是lift
部分函数为总函数。 PartialFunction
定义lift
,将PartialFunction[A,B]
变为A=>Option[B]
。你可以使用这个提升的函数(称之为fun
)来执行:data.map(fun).collect { case Some(x) => x }
答案 1 :(得分:4)
实际上,这在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!)
请参阅applyOrElse上的效率说明。
这是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 }
}
结果:
$ scalac -version
Scala compiler version 2.10.1 -- Copyright 2002-2013, LAMP/EPFL
apm@halyard ~/tmp
$ scalac applyorelse.scala ; scala applyorelse.Test
S: a
S: b
S: c
S: d
S: e
List(a!, b!, c!, d!, e!)
请注意,此版本的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,则代码应该是部分的。
答案 2 :(得分:1)
添加到@stew答案,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.isDefinedAt(x)
。执行scalac -Xprint:typer check.scala
(check.scala
包含您的代码)。它打印:
....
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
}
如您所见,它再次调用unapply
。这就解释了为什么它会打印两次,即一次检查是否已定义,然后在`pf(x)中调用它时接下来。
@ som-snytt是对的。从Scala 2.11开始,TraversableLike
中的收集函数更改为:
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 += _)
)。因此它只打印一次。
答案 3 :(得分:-1)
您可以改为使用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!)
不知道为什么会这样做。