Come viene implementato il pattern matching in Scala a livello di bytecode?


123

Come viene implementato il pattern matching in Scala a livello di bytecode?

È come una serie di if (x instanceof Foo)costrutti o qualcos'altro? Quali sono le sue implicazioni sulle prestazioni?

Ad esempio, dato il codice seguente (dalle pagine 46-48 di Scala By Example ), come sarebbe il codice Java equivalente per il evalmetodo?

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 Posso leggere il bytecode Java, quindi una rappresentazione del bytecode sarebbe abbastanza buona per me, ma probabilmente sarebbe meglio per gli altri lettori sapere come sarebbe come codice Java.

PPS Il libro Programming in Scala dà una risposta a questa e ad altre domande simili su come viene implementato Scala? Ho ordinato il libro, ma non è ancora arrivato.


Perché non compili semplicemente l'esempio e lo disassembla con un disassemblatore di bytecode Java?
Zifre

Probabilmente lo farò, a meno che qualcuno non dia prima una buona risposta. Ma adesso voglio dormire un po '. ;)
Esko Luontola

27
La domanda è utile ad altri lettori!
djondal

1
@djondal: il modo migliore per dirlo è solo votare a favore della domanda :-)
Blaisorblade

Risposte:


96

Il livello basso può essere esplorato con un disassemblatore, ma la risposta breve è che è un mucchio di if / elses in cui il predicato dipende dal pattern

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

C'è molto di più che puoi fare con modelli come o modelli e combinazioni come "case Foo (45, x)", ma generalmente queste sono solo estensioni logiche di ciò che ho appena descritto. I modelli possono anche avere guardie, che sono vincoli aggiuntivi sui predicati. Ci sono anche casi in cui il compilatore può ottimizzare la corrispondenza dei modelli, ad esempio quando c'è qualche sovrapposizione tra i casi potrebbe unire un po 'le cose. I pattern avanzati e l'ottimizzazione sono un'area di lavoro attiva nel compilatore, quindi non sorprenderti se il byte code migliora sostanzialmente rispetto a queste regole di base nelle versioni attuali e future di Scala.

Oltre a tutto ciò, puoi scrivere i tuoi estrattori personalizzati in aggiunta o al posto di quelli predefiniti che Scala usa per le classi dei casi. Se lo fai, il costo della corrispondenza del modello è il costo di qualunque cosa faccia l'estrattore. Una buona panoramica si trova in http://lamp.epfl.ch/~emir/written/MatchingObjectsWithPatterns-TR.pdf


Credo che questo sia il link corrente: infoscience.epfl.ch/record/98468/files/…
greenoldman

78

James (sopra) l'ha detto meglio. Tuttavia, se sei curioso è sempre un buon esercizio guardare il bytecode smontato. Puoi anche invocare scalaccon l' -printopzione, che stamperà il tuo programma con tutte le funzionalità specifiche di Scala rimosse. È fondamentalmente Java nei vestiti di Scala. Ecco l' scalac -printoutput pertinente per lo snippet di codice che hai fornito:

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

34

Dalla versione 2.8, Scala ha l' annotazione @switch . L'obiettivo è garantire che la corrispondenza dei modelli venga compilata in tableswitch o lookupswitch invece che in serie di ifistruzioni condizionali .


6
quando scegliere @switch su normale se altro?
Aravind Yarram

2
l'utilizzo @switchè più efficiente del normale pattern matching. quindi se tutti i casi contengono valori costanti, dovresti sempre usare @switch(perché l'implementazione del bytecode sarà la stessa di java switchinvece di molti if-else)
lev
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.