Wie wird der Mustervergleich in Scala auf Bytecode-Ebene implementiert?

123

Wie wird der Mustervergleich in Scala auf Bytecode-Ebene implementiert?

Ist es wie eine Reihe von if (x instanceof Foo)Konstrukten oder etwas anderes? Was sind die Auswirkungen auf die Leistung?

Wie würde beispielsweise der entsprechende Java-Code für die Methode angesichts des folgenden Codes (von Scala By Example, Seiten 46-48) evalaussehen?

abstract class Expr
case class Number(n: Int) extends Expr
case class Sum(e1: Expr, e2: Expr) extends Expr

def eval(e: Expr): Int = e match {
  case Number(x) => x
  case Sum(l, r) => eval(l) + eval(r)
}

PS Ich kann Java-Bytecode lesen, daher wäre eine Bytecode-Darstellung für mich gut genug, aber wahrscheinlich wäre es für die anderen Leser besser zu wissen, wie es als Java-Code aussehen würde.

PPS Gibt das Buch Programmieren in Scala eine Antwort auf diese und ähnliche Fragen zur Implementierung von Scala? Ich habe das Buch bestellt, aber es ist noch nicht angekommen.

Esko Luontola
quelle
Warum kompilieren Sie das Beispiel nicht einfach und zerlegen es mit einem Java-Bytecode-Disassembler?
Zifre
Ich werde das wahrscheinlich tun, es sei denn, jemand gibt zuerst eine gute Antwort. Aber jetzt möchte ich etwas schlafen. ;)
Esko Luontola
27
Die Frage ist nützlich für andere Leser!
Djondal
1
@ Djondal: Der beste Weg, das zu sagen, ist nur die Frage zu verbessern :-)
Blaisorblade

Antworten:

96

Das niedrige Level kann mit einem Disassembler erkundet werden, aber die kurze Antwort lautet, dass es sich um eine Reihe von if / elses handelt, bei denen das Prädikat vom Muster abhängt

case Sum(l,r) // instance of check followed by fetching the two arguments and assigning to two variables l and r but see below about custom extractors 
case "hello" // equality check
case _ : Foo // instance of check
case x => // assignment to a fresh variable
case _ => // do nothing, this is the tail else on the if/else

Es gibt noch viel mehr, was Sie mit Mustern wie oder Mustern und Kombinationen wie "case Foo (45, x)" tun können, aber im Allgemeinen sind dies nur logische Erweiterungen dessen, was ich gerade beschrieben habe. Muster können auch Schutzvorrichtungen haben, die zusätzliche Einschränkungen für die Prädikate darstellen. Es gibt auch Fälle, in denen der Compiler den Mustervergleich optimieren kann, z. B. wenn sich die Fälle etwas überschneiden, kann dies zu einer gewissen Verschmelzung führen. Erweiterte Muster und Optimierungen sind ein aktiver Arbeitsbereich im Compiler. Seien Sie also nicht überrascht, wenn sich der Bytecode in aktuellen und zukünftigen Versionen von Scala gegenüber diesen Grundregeln erheblich verbessert.

Darüber hinaus können Sie zusätzlich zu oder anstelle der Standardextraktoren, die Scala für Fallklassen verwendet, eigene benutzerdefinierte Extraktoren schreiben. Wenn Sie dies tun, sind die Kosten für die Musterübereinstimmung die Kosten für alles, was der Extraktor tut. Eine gute Übersicht finden Sie unter http://lamp.epfl.ch/~emir/written/MatchingObjectsWithPatterns-TR.pdf

James Iry
quelle
Ich glaube, das ist der aktuelle Link: infoscience.epfl.ch/record/98468/files/…
greenoldman
78

James (oben) sagte es am besten. Wenn Sie jedoch neugierig sind, ist es immer eine gute Übung, sich den zerlegten Bytecode anzusehen. Sie können auch scalacmit der -printOption aufrufen, mit der Ihr Programm gedruckt wird, wobei alle Scala-spezifischen Funktionen entfernt wurden. Es ist im Grunde Java in Scalas Kleidung. Hier ist die relevante scalac -printAusgabe für das von Ihnen angegebene Code-Snippet:

def eval(e: Expr): Int = {
  <synthetic> val temp10: Expr = e;
  if (temp10.$isInstanceOf[Number]())
    temp10.$asInstanceOf[Number]().n()
  else
    if (temp10.$isInstanceOf[Sum]())
      {
        <synthetic> val temp13: Sum = temp10.$asInstanceOf[Sum]();
        Main.this.eval(temp13.e1()).+(Main.this.eval(temp13.e2()))
      }
    else
      throw new MatchError(temp10)
};
Jorge Ortiz
quelle
34

Seit Version 2.8 hat Scala die Annotation @switch . Ziel ist es sicherzustellen, dass der Mustervergleich anstelle einer Reihe von bedingten Anweisungen in einen Tabellen- oder Suchschalter kompiliert wird if.

OM Nom Nom
quelle
6
Wann sollte man @switch anstelle von normal wählen, wenn sonst?
Aravind Yarram
2
Die Verwendung @switchist effizienter als der reguläre Mustervergleich. Wenn also alle Fälle konstante Werte enthalten, sollten Sie immer verwenden @switch(da die Bytecode-Implementierung dieselbe ist wie bei Java switchanstelle vieler if-else)
lev