W jaki sposób dopasowywanie wzorców w Scali jest realizowane na poziomie kodu bajtowego?

123

W jaki sposób dopasowywanie wzorców w Scali jest realizowane na poziomie kodu bajtowego?

Czy to jak seria if (x instanceof Foo)konstrukcji, czy coś innego? Jakie są jego konsekwencje dla wydajności?

Na przykład, biorąc pod uwagę następujący kod (ze stron Scala By Przykład, strony 46-48), jak wyglądałby odpowiednik kodu Java dla tej evalmetody?

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 Potrafię czytać kod bajtowy Javy, więc reprezentacja kodu bajtowego byłaby dla mnie wystarczająca, ale prawdopodobnie byłoby lepiej, aby inni czytelnicy wiedzieli, jak będzie wyglądał kod Java.

PPS Czy książka Programming in Scala zawiera odpowiedź na to i podobne pytania dotyczące implementacji Scali? Zamówiłem książkę, ale jeszcze nie dotarła.

Esko Luontola
źródło
Dlaczego po prostu nie skompilujesz przykładu i nie zdemontujesz go za pomocą dezasemblera kodu bajtowego Java?
Zifre
Prawdopodobnie tak zrobię, chyba że ktoś najpierw udzieli dobrej odpowiedzi. Ale teraz chcę się trochę przespać. ;)
Esko Luontola
27
Pytanie jest przydatne dla innych czytelników!
djondal
1
@djondal: najlepszym sposobem na powiedzenie tego jest po prostu głosowanie za pytaniem :-)
Blaisorblade

Odpowiedzi:

96

Niski poziom można zbadać za pomocą dezasemblera, ale krótka odpowiedź jest taka, że ​​jest to zbiór if / elses, w których predykat zależy od wzorca

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

Jest dużo więcej rzeczy, które możesz zrobić z wzorcami lub wzorcami i kombinacjami, takimi jak „case Foo (45, x)”, ale ogólnie są to tylko logiczne rozszerzenia tego, co właśnie opisałem. Wzorce mogą również mieć osłony, które są dodatkowymi ograniczeniami predykatów. Istnieją również przypadki, w których kompilator może zoptymalizować dopasowanie wzorców, np. Gdy zachodzi pewne nakładanie się przypadków, może to nieco połączyć. Zaawansowane wzorce i optymalizacja są aktywnym obszarem pracy kompilatora, więc nie zdziw się, jeśli kod bajtowy znacznie się poprawi w stosunku do tych podstawowych reguł w obecnych i przyszłych wersjach Scali.

Oprócz tego możesz napisać własne niestandardowe ekstraktory oprócz lub zamiast tych domyślnych, których Scala używa dla klas przypadków. Jeśli tak, to koszt dopasowania wzorca jest kosztem tego, co robi ekstraktor. Dobry przegląd można znaleźć w http://lamp.epfl.ch/~emir/written/MatchingObjectsWithPatterns-TR.pdf

James Iry
źródło
Myślę, że to jest aktualny link: infoscience.epfl.ch/record/98468/files/ ...
greenoldman
78

James (powyżej) powiedział to najlepiej. Jeśli jednak jesteś ciekawy, zawsze dobrym ćwiczeniem jest przyjrzenie się zdemontowanemu kodowi bajtowemu. Możesz także wywołać scalacz -printopcją, która wydrukuje twój program z usuniętymi wszystkimi funkcjami specyficznymi dla Scala. W zasadzie to Java w ubraniach Scali. Oto odpowiednie scalac -printdane wyjściowe dla podanego fragmentu kodu:

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
źródło
34

Od wersji 2.8 Scala ma adnotację @switch . Celem jest zapewnienie, że dopasowywanie wzorców zostanie skompilowane do tablewitch lub lookupswitch zamiast serii ifinstrukcji warunkowych .

mniam mniam mniam
źródło
6
kiedy wybrać @switch zamiast zwykłego, jeśli jeszcze?
Aravind Yarram
2
używanie @switchjest bardziej wydajne niż zwykłe dopasowywanie wzorców. więc jeśli wszystkie przypadki zawierają stałe wartości, powinieneś zawsze używać @switch(ponieważ implementacja kodu bajtowego będzie taka sama jak w java switchzamiast wielu if-else)
lew