web-dev-qa-db-fra.com

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

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

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

Par exemple, étant donné le code suivant (extrait de Scala By Example pages 46-48), à quoi ressemblerait l'équivalent Java code de la méthode eval) comme?

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

P.S. Je peux lire Java bytecode, donc une représentation de bytecode serait assez bonne pour moi, mais il serait probablement préférable pour les autres lecteurs de savoir à quoi cela ressemblerait Java code.

P.P.S. Le livre Programmation en Scala donne-t-il une réponse à cette question et à des questions similaires sur la façon dont Scala est implémenté? J'ai commandé le livre, mais il n'est pas encore arrivé.

121
Esko Luontola

Le bas niveau peut être exploré avec un désassembleur mais la réponse courte est que c'est un tas d'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 modèles, par exemple quand il y a un certain chevauchement entre les cas, il peut unir les choses un peu. 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 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

94
James Iry

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 scalac avec le -print option, 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 le scalac -print sortie 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)
};
76
Jorge Ortiz

Depuis la version 2.8, Scala a eu l'annotation @ switch . Le but est de s'assurer que la correspondance des motifs sera compilée dans tableswitch ou lookupswitch = au lieu d'une série d'instructions conditionnelles if.

32
om-nom-nom