Scala提取器未应用地调用了两次

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

我刚刚发现我的提取器中的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是非常重要的,所以我当然不想调用它两次

您可以改为使用
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
}