2

我刚刚发现由于某种原因,我的提取器中的 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 并不简单,所以我当然不想调用它两次!

4

4 回答 4

6

collect 将 aPartialFunction作为输入。 PartialFunction定义了两个关键成员:isDefinedAtapply。当 collect 运行你的函数时,它会运行你的提取器一次以确定你的函数是否isDefinedAt有一些特定的输入,如果是,那么它会再次运行提取器作为apply提取值的一部分。

如果有正确实现 isDefinedAt 的简单方法,您可以通过显式实现自己的 PartialFunction 来自己实现,而不是使用 case 语法。或者您可以在集合上使用一个总函数执行 a filterand then map(这本质上是 collect 通过调用isDefinedAt, then所做的apply

另一种选择是将lift部分函数转换为总函数。 PartialFunction定义lift将 aPartialFunction[A,B]变成 a 的A=>Option[B]。你可以使用这个提升的函数(调用它fun)来做:data.map(fun).collect { case Some(x) => x }

于 2013-06-11T16:56:21.847 回答
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,代码应该是部分的。

于 2013-06-11T22:08:12.880 回答
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.scalacheck.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 开始,collect 函数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
}

它只打印一次的原因是,它在内部调用applyOrElsewhich 检查它是否已定义。如果是,则在其中应用该功能(在上述情况下(b += _))。因此它只打印一次。

于 2013-06-12T07:02:19.510 回答
-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!)

不知道为什么它会起作用。

于 2013-06-11T16:53:37.280 回答