2

エクストラクターで unapply が何らかの理由で 2 回呼び出されていることを発見しました。誰もが理由とそれを回避する方法を知っていますか?

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 は自明ではないため、2 回呼び出したくはありません。

4

4 に答える 4

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.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ご覧のとおり、ここで再び呼び出します。これは、2 回出力される理由を説明しています。つまり、定義されているかどうかを確認するために 1 回出力し、次に `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
}

一度だけ出力する理由は、applyOrElse定義されているかどうかをチェックする内部的に呼び出すためです。はいの場合、関数が適用されます(上記の場合(b += _))。したがって、一度だけ印刷されます。

于 2013-06-12T07:02:19.510 に答える