2 votes

Scala Extractor unapply appelé deux fois

Je viens de découvrir que la fonction "unapply" de mon extracteur est appelée deux fois pour une raison quelconque. Quelqu'un sait-il pourquoi, et comment l'éviter ?

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 } )

Cela produit un résultat :

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!)

Le résultat final est bon, mais dans mon programme réel, la dépose n'est pas triviale, et je ne veux certainement pas l'appeler deux fois !

6voto

stew Points 5897

Collecter prend un PartialFunction comme entrée. PartialFunction définit deux membres clés : isDefinedAt y apply . Lorsque collect exécute votre fonction, il exécute votre extracteur une fois pour déterminer si votre fonction isDefinedAt une entrée particulière, et si c'est le cas, il exécute à nouveau l'extracteur dans le cadre de apply pour extraire la valeur.

S'il existe un moyen trivial d'implémenter correctement isDefinedAt, vous pourriez le faire vous-même en implémentant votre propre PartialFunction explicitement, au lieu d'utiliser la syntaxe case. ou vous pourriez faire un filter et ensuite map avec une fonction totale sur la collection (ce qui est essentiellement ce que fait collect en appelant isDefinedAt entonces apply )

Une autre option serait de lift la fonction partielle à une fonction totale. PartialFunction définit lift qui transforme un PartialFunction[A,B] en un A=>Option[B] . Vous pouvez utiliser cette fonction levée (appelez-la fun ) à faire : data.map(fun).collect { case Some(x) => x }

4voto

som-snytt Points 17224

En fait, ce problème a été traité dans la version 2.11 comme un bogue de performance :

$ 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!)

Voir les notes d'efficacité sur applyOrElse .

Voici une version pour 2.10, où le problème est facilement résolu par extension :

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 }
}

Résultat :

$ 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!)

Notez que cette version de l'Uap est partielle :

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)

Je pense que si le cas d'utilisation est PF, le code devrait être partiel.

1voto

Jatin Points 8069

Ajout à la réponse de @stew, collect est mis en œuvre comme :

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
}

Il utilise pf.isDefinedAt(x) . Faire scalac -Xprint:typer check.scala ( check.scala contient votre code). Il imprime :

....
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
    }

Donc, comme vous le voyez, il appelle unapply ici encore. Cela explique pourquoi il s'imprime deux fois, c'est-à-dire une fois pour vérifier s'il est défini et ensuite quand il est déjà appelé dans `pf(x).

@som-snytt a raison. Depuis la version 2.11 de Scala, la fonction collect de TraversableLike est changé en :

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
}

La raison pour laquelle il n'imprime qu'une seule fois est que, en interne, il appelle applyOrElse qui vérifie s'il est défini. Si oui, il applique la fonction elle-même (dans le cas ci-dessus (b += _) ). Il n'est donc imprimé qu'une seule fois.

-1voto

Cosmia Luna Points 111

Vous pouvez utiliser map à la place :

scala>    println( data.map{ case Uap(x) => x } )
S: a
S: b
S: c
S: d
S: e
List(a!, b!, c!, d!, e!)

Je ne sais pas pourquoi ça marche comme ça.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X