Äquivalent zum idiomatischen Mustervergleich in Java


9

Ich baue einen Simulator, der einige Ereignisse analysiert STDINund "ausführt". Mein Hintergrund ist heutzutage hauptsächlich funktionale Programmierung, daher schien es naheliegend, so etwas zu tun:

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

wo simulieren wäre

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

usw. Was ist die idiomatische Art, solche Dinge in Java zu tun? Googeln hat mich in Richtung verschachtelter Klassen und des Besuchermusters gelenkt, aber das scheint bei meinem Versuch ziemlich schwer zu sein. Typ Löschung scheint mich hart zu bekämpfen. Können Sie mir einen Überblick darüber geben, wie das richtig gemacht aussehen würde?


1
Hängt wahrscheinlich in gewissem Maße vom Typ ab. Können Sie kurz beschreiben, was Event und seine int / string-Mitglieder bedeuten? Ist der EventTyp beispielsweise konzeptionell gleichbedeutend mit eins Intund zwei Maybe Strings?
Ixrec

1
Ist Java die Sprache, die Sie wollen oder mit der Sie arbeiten müssen?
Thomas Junk

Der Mustervergleich kann eine zukünftige Funktion in Java 1x sein, die in JEP 305 beschrieben wird .
tsh

Antworten:


10

Der Autor von 'Functional Programming in Scala' gibt eine schöne Illustration des Besten, was in Java auf typsichere Weise erreicht werden kann:

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

Im Wesentlichen wird eine Church-Codierung der Fälle verwendet, um sicherzustellen, dass sich der Compiler beschwert, wenn welche fehlen.

Die Einzelheiten sind nicht ohne weiteres zusammengefasst und sind in der Tat so gut in dem Artikel behandelt , dass es keinen Sinn , sie hier bei der Wiedergabe (das ist , was Hyperlinks sind für richtig?).


Danke, das ist mehr oder weniger das, wonach ich gesucht habe. Der Besucher hat in diesem Fall einigermaßen gut gearbeitet, aber ich werde wahrscheinlich in Zukunft das Beispiel der Kirche verwenden.
Closeparen

2
Ich denke, das ist klug, aber ich bin nicht sicher, ob es idiomatisch ist
Brian Agnew

Das idiomatische Äquivalent ist der doppelte Versand, bei dem Sie ein Objekt t.matchmit den drei Methoden übergeben, anstatt drei Funktionen zu übergeben. (Der verlinkte Artikel verwechselt Besuchermuster mit doppeltem Versand - Besucher ist ein Muster zum Abstrahieren der Iteration über ein Netzwerk, nicht zum Auswählen von Mustern)
Pete Kirkham

Der doppelte Versand ist eine Möglichkeit , das Besuchermuster zu implementieren . Ich habe keine Ahnung, was Sie unter "Abstrahieren der Iteration über ein Netzwerk" verstehen - das ist kein Ausdruck, der in der GoF-Definition von "Besucher" verwendet wird.
NietzscheanAI

Ich habe Probleme mit diesem Blog / Artikel, weil er damit in seinem "schlechten" Beispiel beginnt: public static int depth(Tree t)wo er verkettet verwendet, wenn eine Instanz davon, wenn der "richtige" Weg, dies in Java zu tun, darin besteht, eine Schnittstelle mit der Methode zu definieren: public int depth()und Verwenden Sie Polymorphismus. Es scheint wie ein Strohmann.
JimmyJames

4

Was ist die idiomatische Art, so etwas in Java zu tun?

Es gibt so etwas nicht wirklich, da Java (die Sprache) grundsätzlich unerlässlich ist.

Wenn Sie auf der JVM ausgeführt werden können, aber nicht auf die Java- Sprache beschränkt sind , können Sie Scala untersuchen, mit dem mithilfe des Mustervergleichs etwas wie oben erreicht werden kann .

Ansonsten sind Sie meiner Meinung nach darauf beschränkt, Ihre verschiedenen Fälle manuell anzupassen und Methoden entsprechend aufzurufen oder Subtypen von 'Event' zu definieren und mithilfe von Polymorphismus bestimmte Methoden für jeden Subtyp aufzurufen.


Ich denke, dein letzter Absatz trifft den Nagel auf den Kopf. Der idiomatische Ansatz hierfür in Java ist die Verwendung von Polymorphismus. Event ist eine Schnittstelle mit einer geeigneten Methode, und Thing1, Thing2 und Thing3 sind Implementierungen dieser Schnittstelle. Es teilt den Code eher nach Typ als nach Funktion auf, aber das ist im Grunde der Punkt von OOP, nicht wahr?
Jules

Java als Imperativ ist unerheblich. Java könnte (und sollte) Summentypen integrieren, tut es aber einfach nicht.
Gardenhead

1

Schauen Sie sich https://github.com/johnlcox/motif an , eine Scala-ähnliche "Pattern Matching" -Bibliothek für Java 8.

Nicht annähernd so schön wie ML / Erlang / Haskell, sieht aber dennoch viel aussagekräftiger aus als die meisten anderen.


0

Sie können eine Aufzählung und eine Schnittstelle verwenden, um die Simulationsmethode wie folgt zu überschreiben:

interface Event {
  void simulate()
}

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

Nehmen wir an, Sie haben Event event. Dann können Sie es auf zwei Arten verwenden:

event.simulate();

oder

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

Der Konstruktor wird jedoch automatisch von der JVM aufgerufen. Um also auch die Parameter dort zu speichern, müssen Sie Eigenschaften mit Accessoren usw. hinzufügen.

Alternativ können Sie Ihre Ereignisse als konstante Zeichenfolgen codieren und das switchKonstrukt verwenden. In diesem Fall würden Sie dies tun

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

usw. Aber ja, es gibt nichts Natives, das den Mustervergleich direkt nachahmt :(


Ich bin mir nicht sicher, warum Sie hier eher eine Aufzählung als Klassen verwenden - was ist der Vorteil der Aufzählung?
Jules

Denn dann können Sie es auch in einer switch-Anweisung verwenden, was es etwas mehr wie Pattern Matching macht
jasiek.miko

fügte das jetzt hinzu.
jasiek.miko

0

Das Besuchermuster oder sein kirchliches Kodierungsäquivalent ist der richtige Weg. In Java ist es ziemlich ausführlich, aber hoffentlich können Tools wie Derive4J (ein von mir gepflegter Anmerkungsprozessor) oder Adt4J das Boilerplate generieren. Mit einem solchen Tool wird Ihr Beispiel:

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 generiert die Events-Klasse, die eine fließende Mustervergleichssyntax bietet (mit umfassender Überprüfung, ob alle Fälle behandelt werden).


Könnten Sie in Ihrem Beitrag Ihre Zugehörigkeit zum Projekt klarstellen (siehe stackoverflow.com/help/promotion ) ?
Benni
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.