2010-10-31 13 views
12

Das ist etwas, worüber ich mich schon eine Weile gewundert habe. Ich sehe dieses Muster eine Menge:Ist das PartialFunction-Design ineffizient?

if (pf.isDefinedAt(in)) pf(in) 

Durch diese Zerschlagung in zwei getrennte Anrufe, alle Muster, die in #isDefinedAt ausgewertet wurden, werden dann auch in #apply ausgewertet. Zum Beispiel:

object Ex1 { 
    def unapply(in: Int) : Option[String] = { 
    println("Ex1") 
    if (in == 1) Some("1") else None 
    } 
} 

object Ex2 { 
    def unapply(in: Int) : Option[String] = { 
    println("Ex2") 
    if (in == 2) Some("2") else None 
    } 
} 

val pf : PartialFunction[Int,String] = { 
    case Ex1(result) => result 
    case Ex2(result) => result 
} 

val in = 2 

if (pf.isDefinedAt(in)) pf(in) 

Welche druckt

Ex1 
Ex2 
Ex1 
Ex2 
res52: Any = 2 

Im schlimmsten Fall, wo Ihr Muster letzten Spiele, haben Sie Ihre Muster/Extraktoren zweimal ausgewertet, wenn eine Partielle Funktion aufrufen. Dies könnte beim Abgleich über benutzerdefinierte Extraktoren, die mehr als nur eine einfache Klassen- oder Listenmusterübereinstimmung aufwiesen, ineffizient werden (z. B. wenn Sie einen Extraktor hatten, der ein XML-Dokument analysierte und einige Wertobjekte zurückgab)

PartialFunction # lift leidet die gleiche Doppelauswertung:

scala> pf.lift(2) 
Ex1 
Ex2 
Ex1 
Ex2 
res55: Option[String] = Some(2) 

gibt es eine Möglichkeit bedingt eine Funktion aufzurufen, wenn es zweimal ohne potenziell ruft alle Ihre Extraktoren definiert ist?

Antwort

15

Es gibt a conversation going on about thisjetzt auf das scala-Interna Mailingliste. Martin Odersky hat einen neuen Typ vorgeschlagen: FunctionWithDefault. Martin spricht nicht nur von einer Laufzeitstrafe, sondern eine Kompilierung Strafe (von aufblasen Klasse-Datei) der Verwendung von PartialFunction:

Zuerst müssen wir den Pattern-Matching-Code zweimal, einmal in der Anwendung zu erzeugen, und dann wieder im isDefinedAt. Zweitens müssen wir den Code auch zweimal ausführen, um zu testen, ob die Funktion anwendbar ist, und sie dann tatsächlich anzuwenden.

Die Antwort auf Ihre Frage ist im Wesentlichen mit „Ja“ und und dieses Verhalten (von PartialFunction) nicht entweder aufgrund der Abwärtskompatibilität Fragen ändern (zum Beispiel was passiert, wenn die isDefinedAt Seiten bewirkende).

Der neue Typ vorgeschlagen wird, hat keine FunctionWithDefaultisDefinedAt und hat eine Methode:

trait FunctionWithDefault[-I, +O] { 
    def applyOrElse[OO >: O](i : I, default : I => OO) : OO 
} 

, die ein wenig wie Option s getOrElse Methode wirkt.

Ich muss sagen, dass, wie üblich, kann ich mir nicht vorstellen, dass diese Ineffizienz irgendeine Art von Leistung Problem in der überwältigenden Mehrheit der Fälle darstellt.

+0

Vielen Dank für die ausführliche Antwort! FunctionWithDefault klingt genau nach dem was ich gesucht habe. – Collin

+1

Zweimal richtiges Pattern-Matching ist ein großer Performance-Hit, wenn Sie komplexe Match-Anweisungen in performancekritischem Code haben! Und wenn 'default' mit Namen übergeben wird, wie soll das effizienter sein als eine Option zurückgeben? Vielleicht sollte ich scala-internals lesen. –

+0

Ich habe das sig-default falsch, eher bizarr ist es ein 'I => OO' –

Verwandte Themen