【问题标题】:Scala Extractor unapply called twiceScala Extractor unapply 调用了两次
【发布时间】:2013-06-11 16:46:07
【问题描述】:

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

【问题讨论】:

    标签: scala case collect extractor


    【解决方案1】:

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

    如果有正确实现 isDefinedAt 的简单方法,您可以通过显式实现自己的 PartialFunction 来自己实现,而不是使用 case 语法。或者你可以在集合上执行一个filter,然后是map(这基本上就是collect通过调用isDefinedAt,然后是apply所做的)

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

    【讨论】:

    • 另一种选择是缓存部分函数对象中的最后一个值。
    • @Greg 这是真的,但有点老派,因为它是一个在 2.11 中不正确的实现细节,在 2.10 中有一些不错的修复。
    【解决方案2】:

    实际上,这已在 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,代码应该是部分的。

    【讨论】:

      【解决方案3】:

      添加到@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 开始,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 += _))。因此它只打印一次。

      【讨论】:

        【解决方案4】:

        您可以改用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!)
        

        不知道为什么会这样。

        【讨论】:

        • 不要以为我可以。在我的实际应用程序中,我实际上是在 unapply 中执行选择逻辑,因此它并不总是返回 Some(something)。地图总是需要一对一的结果。
        • 由于 OP 模式匹配不是部分的,我认为投反对票是不公平的。用例很重要。
        猜你喜欢
        • 2017-06-25
        • 2013-01-21
        • 2012-09-08
        • 2016-04-27
        • 2019-02-27
        • 1970-01-01
        • 2019-12-04
        • 2018-09-15
        • 2012-10-10
        相关资源
        最近更新 更多