Comment la correspondance de modèles dans Scala est-elle implémentée au niveau du bytecode?

123

Comment la correspondance de modèles dans Scala est-elle implémentée au niveau du bytecode?

Est-ce comme une série de if (x instanceof Foo)constructions, ou autre chose? Quelles sont ses implications en termes de performances?

Par exemple, étant donné le code suivant (extrait des pages 46 à 48 de Scala By Example ), à quoi evalressemblerait le code Java équivalent pour la méthode?

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 Je peux lire le bytecode Java, donc une représentation bytecode serait assez bonne pour moi, mais il serait probablement préférable que les autres lecteurs sachent à quoi cela ressemblerait en tant que code Java.

PPS Le livre Programming in Scala donne-t-il une réponse à cette question et à des questions similaires sur la manière dont Scala est implémentée? J'ai commandé le livre, mais il n'est pas encore arrivé.

Esko Luontola
la source
Pourquoi ne pas simplement compiler l'exemple et le démonter avec un désassembleur de bytecode Java?
Zifre
Je vais probablement le faire, à moins que quelqu'un ne donne d'abord une bonne réponse. Mais maintenant, je veux dormir. ;)
Esko Luontola
27
La question est utile aux autres lecteurs!
djondal le
1
@djondal: la meilleure façon de dire cela est juste de voter pour la question :-)
Blaisorblade

Réponses:

96

Le niveau bas peut être exploré avec un désassembleur mais la réponse courte est que c'est un tas de if / elses où le prédicat dépend du modèle

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

Il y a beaucoup plus que vous pouvez faire avec des modèles comme ou des modèles et des combinaisons comme "case Foo (45, x)", mais généralement ce ne sont que des extensions logiques de ce que je viens de décrire. Les modèles peuvent également avoir des gardes, qui sont des contraintes supplémentaires sur les prédicats. Il y a aussi des cas où le compilateur peut optimiser la correspondance de motifs, par exemple quand il y a un chevauchement entre les cas, il peut un peu fusionner les choses. Les modèles avancés et l'optimisation sont un domaine de travail actif dans le compilateur, alors ne soyez pas surpris si le code d'octet s'améliore considérablement par rapport à ces règles de base dans les versions actuelles et futures de Scala.

En plus de tout cela, vous pouvez écrire vos propres extracteurs personnalisés en plus ou à la place de ceux par défaut que Scala utilise pour les classes de cas. Si vous le faites, le coût de la correspondance de modèle est le coût de tout ce que fait l'extracteur. Un bon aperçu se trouve dans http://lamp.epfl.ch/~emir/written/MatchingObjectsWithPatterns-TR.pdf

James Iry
la source
Je crois que c'est le lien actuel: infoscience.epfl.ch/record/98468/files/…
greenoldman
78

James (ci-dessus) l'a dit le mieux. Cependant, si vous êtes curieux, c'est toujours un bon exercice de regarder le bytecode démonté. Vous pouvez également appeler scalacavec l' -printoption, qui imprimera votre programme avec toutes les fonctionnalités spécifiques à Scala supprimées. C'est essentiellement Java dans les vêtements de Scala. Voici la scalac -printsortie pertinente pour l'extrait de code que vous avez donné:

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
la source
34

Depuis la version 2.8, Scala a l' annotation @switch . Le but est de s'assurer que la correspondance de modèle sera compilée dans tablewitch ou lookupswitch au lieu d'une série d' ifinstructions conditionnelles .

Om Nom Nom
la source
6
quand choisir @switch plutôt que normal sinon?
Aravind Yarram
2
l'utilisation @switchest plus efficace que la correspondance de motif classique. donc si tous les cas contiennent des valeurs constantes, vous devez toujours utiliser @switch(car l'implémentation du bytecode sera la même que celle de java switchau lieu de plusieurs if-else)
lev