Como a correspondência de padrões no Scala é implementada no nível do bytecode?

123

Como a correspondência de padrões no Scala é implementada no nível do bytecode?

É como uma série de if (x instanceof Foo)construções, ou algo mais? Quais são as implicações de desempenho?

Por exemplo, dado o código a seguir (das páginas 46 a 48 do Scala By Example ), como seria o código Java equivalente para o evalmétodo?

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 ler bytecode Java, portanto, uma representação de bytecode seria boa o suficiente para mim, mas provavelmente seria melhor para os outros leitores saberem como seria o código Java.

PPS O livro Programming in Scala dá uma resposta a esta e outras perguntas semelhantes sobre como o Scala é implementado? Eu pedi o livro, mas ele ainda não chegou.

Esko Luontola
fonte
Por que você não apenas compila o exemplo e o desmonte com um desmontador de bytecodes Java?
Zifre 15/04/09
Provavelmente vou fazer isso, a menos que alguém dê uma boa resposta primeiro. Mas agora eu quero dormir um pouco. ;)
Esko Luontola
27
A pergunta é útil para outros leitores!
djondal
1
@djondal: a melhor maneira de dizer que é apenas para upvote a questão :-)
Blaisorblade

Respostas:

96

O nível baixo pode ser explorado com um desmontador, mas a resposta curta é que são vários casos em que o predicado depende do padrão

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

Você pode fazer muito mais com padrões como ou padrões e combinações como "case Foo (45, x)", mas geralmente essas são apenas extensões lógicas do que acabei de descrever. Os padrões também podem ter proteções, que são restrições adicionais nos predicados. Também existem casos em que o compilador pode otimizar a correspondência de padrões, por exemplo, quando há alguma sobreposição entre os casos, ele pode se unir um pouco. Padrões avançados e otimização são uma área ativa de trabalho no compilador; portanto, não se surpreenda se o código de bytes melhorar substancialmente essas regras básicas nas versões atuais e futuras do Scala.

Além de tudo isso, você pode escrever seus próprios extratores personalizados, além dos ou padrão, que o Scala usa para as classes de caso. Se o fizer, o custo da correspondência de padrões é o custo do que quer que o extrator faça. Uma boa visão geral é encontrada em http://lamp.epfl.ch/~emir/written/MatchingObjectsWithPatterns-TR.pdf

James Iry
fonte
Eu acredito que este é o link atual: infoscience.epfl.ch/record/98468/files/…
greenoldman
78

James (acima) disse isso melhor. No entanto, se você estiver curioso, é sempre um bom exercício observar o código de código desmontado. Você também pode chamar scalaccom a -printopção, que imprimirá seu programa com todos os recursos específicos do Scala removidos. É basicamente Java nas roupas de Scala. Aqui está a scalac -printsaída relevante para o snippet de código que você forneceu:

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
fonte
34

Desde a versão 2.8, o Scala possui a anotação @switch . O objetivo é garantir que a correspondência de padrões seja compilada no comutador de tabela ou no modo de pesquisa, em vez de uma série de ifinstruções condicionais .

om-nom-nom
fonte
6
quando escolher @switch em vez de regular, se mais?
Aravind Yarram
2
o uso @switché mais eficiente que a correspondência regular de padrões. por isso, se todos os casos contêm valores constantes, você deve sempre usar @switch(porque a implementação bytecode será o mesmo a partir de java switchem vez de muitos if-else)
lev