Correspondência de padrão linguístico equivalente em Java


9

Estou construindo um simulador que analisa alguns eventos STDINe os "executa". Atualmente, minha formação é principalmente a programação funcional, por isso parecia natural fazer algo assim:

data Event = Thing1 String Int | Thing2 Int | Thing3 String String Int
Parse :: String -> [Event]
Simulate :: [Event] -> [Result]

onde simular seria

case event
  of Thing1 a b => compute for thing one
   | Thing2 a => compute for thing two

etc. Qual é a maneira idiomática de fazer esse tipo de coisa em Java? O Google me apontou na direção de classes aninhadas e do padrão de visitantes, mas isso parece bastante pesado na minha tentativa. O apagamento de tipo parece estar lutando comigo, com força. Você poderia me mostrar um resumo de como isso seria feito corretamente?


1
Provavelmente depende do tipo até certo ponto. Você poderia descrever brevemente o que significa Event e seus membros int / string? Por exemplo, o Eventtipo é conceitualmente equivalente a ter um Inte dois Maybe Strings?
Ixrec

1
Java é a linguagem que você deseja ou precisa trabalhar?
Thomas Junk

A correspondência de padrões pode ser um recurso futuro em Java 1x, descrito no JEP 305 .
TSH

Respostas:


10

O autor de 'Functional Programming in Scala' fornece uma boa ilustração do melhor que pode ser alcançado em Java de uma maneira segura:

http://blog.higher-order.com/blog/2009/08/21/structural-pattern-matching-in-java/

Essencialmente, ele usa uma codificação da Igreja dos casos para garantir que o compilador irá reclamar, se houver algum.

Os detalhes não são facilmente resumidos e de fato são tão bem coberto no artigo que não há nenhum ponto em reproduzi-los aqui (que é o que hyperlinks são para certo?).


Obrigado, isso é mais ou menos o que eu estava procurando. O visitante acabou trabalhando razoavelmente bem neste caso, mas provavelmente usarei o exemplo da Igreja no futuro.
closeparen

2
Eu acho que é inteligente, mas não tenho certeza se é idiomático #
Brian Agnew

O equivalente idiomático é o despacho duplo, onde você passa um objeto para t.matchos três métodos, em vez de passar três funções. (Artigo ligado confunde padrão Visitor com o dobro da expedição - visitante é um padrão para abstrair a iteração através de uma rede, e não para a seleção em padrões)
Pete Kirkham

O envio duplo é uma maneira de implementar o padrão Visitor. Não tenho idéia do que você quer dizer com 'abstraindo a iteração pela rede' - essa não é uma frase usada na definição do GoF de Visitor.
NietzscheanAI

Estou lutando com este blog / artigo porque ele começa com isso em seu exemplo 'ruim': public static int depth(Tree t)onde ele usa encadeado se uma instância de quando a maneira 'certa' de fazer isso em Java é definir uma interface com o método: public int depth()e use polimorfismo. Parece um homem de palha.
JimmyJames

4

Qual é a maneira idiomática de fazer esse tipo de coisa em Java?

Realmente não existe, dado que Java (a linguagem) é fundamentalmente imperativo.

Se você puder executar na JVM, mas não se restringir à linguagem Java , poderá investigar o Scala, que alcançaria algo parecido com o acima, usando a correspondência de padrões .

Caso contrário, acho que você está reduzido a corresponder manualmente seus vários casos e chamar métodos conforme apropriado, ou talvez definir subtipos de 'Event' e usar o polimorfismo para invocar métodos específicos para cada subtipo.


Eu acho que o seu último parágrafo atinge a unha na cabeça. A abordagem idiomática para isso em Java é usar polimorfismo. Faça Event ser uma interface com um método apropriado e Thing1, Thing2 e Thing3 sejam implementações dessa interface. Ele divide o código por tipo e não por função, mas esse é basicamente o objetivo do OOP, não é?
Jules

Ser imperativo para Java é imaterial. Java pode (e deve) integrar tipos de soma, mas simplesmente não.
gardenhead

1

Dê uma olhada em https://github.com/johnlcox/motif, que é uma biblioteca de "correspondência de padrões" semelhante a Scala para Java 8.

Não é tão bom quanto ML / Erlang / Haskell, mas ainda parece muito mais declarativo do que a maioria.


0

Você pode usar uma enumeração e uma interface, substituindo o método simular, assim:

interface Event {
  void simulate()
}

enum MyEvents implements Event {
  THING1 {
    @Override
    void simulate() {
    //...
    }
  },
  THING2 {
    @Override
    void simulate() {
    //...
    }
  },
}

Digamos que você tenha Event event. Em seguida, você pode usá-lo de duas maneiras:

event.simulate();

ou

switch(event) {
  case THING1:
    //..
    break;
  case THING2:
    break;
}

Mas o construtor é chamado automaticamente pela JVM, para também armazenar os parâmetros, você precisará adicionar propriedades aos acessadores, etc.

Como alternativa, você pode codificar seus eventos como seqüências constantes e usar a switchconstrução. Nesse caso, você faria

string event = args[0];
switch(event){
  case THING1:
    int a = args[1];
    //...
    break;
  case THING2:
    int a = args[1];
    int b = args[2];
    break;
}

etc. Mas sim, não há nada nativo que imite diretamente a correspondência de padrões :(


Não sei por que você está usando um enum aqui, em vez de classes - qual é o benefício do enum?
Jules

Porque então você também pode usá-lo em uma instrução switch, o que torna um pouco mais como correspondência de padrões
jasiek.miko

adicionou isso agora.
Jasiek.miko # 10/16

0

O padrão de visitantes ou seu equivalente de codificação de igreja é o caminho a seguir. É bastante detalhado em Java, mas espero que ferramentas como Derive4J (um processador de anotação que mantenho) ou Adt4J possam gerar o clichê. Usando essa ferramenta, seu exemplo se torna:

import java.util.function.Function;
import org.derive4j.Data;

@Data
public abstract class Event {

  interface Cases<X> {
    X Thing1(String s, int i);
    X Thing2(int i);
    X Thing3(String s, String s2, int i);
  }

  abstract <X> X match(Cases<X> cases);

  static Function<Event, Result> Simulate =
      Events.cases().
          Thing1( (s, i    ) -> computeForThingOne(s, i)       ).
          Thing2( (i       ) -> computeForThingTwo(i)          ).
          Thing3( (s, s2, i) -> computeForThingThree(s, s2, i) );

}

Derive4J gera a classe Events que fornece uma sintaxe fluente de correspondência de padrões (com verificação exaustiva de que todos os casos são tratados).


Você poderia esclarecer sua afiliação ao projeto em sua postagem (consulte stackoverflow.com/help/promotion ).
Benni
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.