Equivalente de patrones idiomáticos en Java


9

Estoy construyendo un simulador que analiza algunos eventos STDINy los "ejecuta". Mi experiencia es principalmente programación funcional en estos días, por lo que parecía natural hacer algo como esto:

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

donde simular sería

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

¿Cuál es la forma idiomática de hacer este tipo de cosas en Java? Buscar en Google me ha señalado en la dirección de las clases anidadas y el patrón de visitante, pero eso parece bastante pesado en mi intento. El borrado de tipo parece estar luchando contra mí, duro. ¿Podrías mostrarme un resumen de cómo se vería correctamente?


1
Probablemente depende del tipo hasta cierto punto. ¿Podría describir brevemente qué significan Event y sus miembros int / string? Por ejemplo, ¿es el Eventtipo conceptualmente equivalente a tener uno Inty dos Maybe Strings?
Ixrec

1
¿Es Java el lenguaje que desea o con el que tiene que trabajar?
Thomas Junk

La coincidencia de patrones puede ser una característica futura en Java 1x que se describe en JEP 305 .
tsh

Respuestas:


10

El autor de 'Programación funcional en Scala' ofrece una buena ilustración de lo mejor que se puede lograr en Java de una manera segura:

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

Esencialmente, utiliza una codificación de la Iglesia de los casos para garantizar que el compilador se quejará si falta alguno.

Los detalles no se resumen fácilmente y de hecho están tan bien cubiertos en el artículo que no tiene sentido en la reproducción de ellos aquí (eso es lo que son los hipervínculos de verdad?).


Gracias, esto es más o menos lo que estaba buscando. El visitante terminó trabajando razonablemente bien para este caso, pero probablemente usaré el ejemplo de la Iglesia en el futuro.
closeparen

2
Creo que es inteligente, pero no estoy seguro de que sea idiomático
Brian Agnew,

El equivalente idiomático es el envío doble, donde se pasa un objeto t.matchcon los tres métodos en lugar de pasar tres funciones. (el artículo vinculado confunde el patrón de Visitante con el envío doble: el visitante es un patrón para abstraer la iteración a través de una red, no para seleccionar patrones)
Pete Kirkham

El envío doble es una forma de implementar el patrón Visitante. No tengo idea de lo que quieres decir con "abstraer la iteración a través de una red"; esa no es una frase utilizada en la definición de visitante de GoF.
NietzscheanAI

Estoy luchando con este blog / artículo porque comienza con esto en su "mal" ejemplo: public static int depth(Tree t)donde usa encadenado si una instancia de cuando la forma "correcta" de hacer esto en Java es definir una interfaz con el método: public int depth()y usar polimorfismo Parece un hombre de paja.
JimmyJames

4

¿Cuál es la forma idiomática de hacer este tipo de cosas en Java?

Realmente no existe tal cosa, dado que Java (el lenguaje) es fundamentalmente imprescindible.

Si puede ejecutar en la JVM, pero no está restringido al lenguaje Java , podría investigar Scala, que lograría algo como lo anterior utilizando la coincidencia de patrones .

De lo contrario, creo que se verá reducido a hacer coincidir manualmente sus diversos casos y métodos de llamada según corresponda, o tal vez definir subtipos de 'Evento' y usar polimorfismo para invocar métodos particulares para cada subtipo.


Creo que tu último párrafo da en el clavo. El enfoque idiomático de esto en Java es usar polimorfismo. Haga que Event sea una interfaz con un método apropiado, y Thing1, Thing2 y Thing3 sean implementaciones de esa interfaz. Divide el código por tipo en lugar de por función, pero ese es básicamente el objetivo de OOP, ¿no?
Julio

Que Java sea imperativo es irrelevante. Java podría (y debería) integrar tipos de suma, pero simplemente no lo hace.
cabeza de jardín

1

Eche un vistazo a https://github.com/johnlcox/motif, que es una biblioteca de "coincidencia de patrones" similar a Scala para Java 8.

No es tan bueno como ML / Erlang / Haskell, pero aún se ve mucho más declarativo que la mayoría.


0

Puede usar una enumeración e interfaz, anulando el método de simulación, como este:

interface Event {
  void simulate()
}

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

Digamos que tienes Event event. Luego puede usarlo de dos maneras:

event.simulate();

o

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

Pero la JVM llama automáticamente al constructor, por lo que para almacenar los parámetros allí, tendrá que agregar propiedades con accesores, etc.

Alternativamente, puede codificar sus eventos como cadenas constantes y usar la switchconstrucción, en cuyo caso lo haría

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. Pero sí, no hay nada nativo que imite directamente la coincidencia de patrones :(


No estoy seguro de por qué estás usando una enumeración aquí en lugar de clases: ¿cuál es el beneficio de la enumeración?
Julio

Porque entonces también puede usarlo en una declaración de cambio, lo que lo hace un poco más parecido a la coincidencia de patrones
jasiek.miko

agregó eso ahora.
jasiek.miko

0

El patrón de visitante o su equivalente de codificación de la iglesia es el camino a seguir. Es bastante detallado en Java, pero es de esperar que herramientas como Derive4J (un procesador de anotaciones que mantengo) o Adt4J puedan generar el repetitivo. Usando tal herramienta, su ejemplo se convierte en:

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 genera la clase Eventos que proporciona una sintaxis de coincidencia de patrones fluida (con una verificación exhaustiva de que se manejan todos los casos).


¿Podría aclarar su afiliación con el proyecto en su publicación (consulte stackoverflow.com/help/promotion ).
Benni
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.