8

I'm building a simulator which parses some events from STDIN and "runs" them. My background is mostly functional programming these days, so it seemed natural to do something like this:

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

where simulate would be

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

etc. What is the idiomatic way to do this sort of thing in Java? Googling has pointed me in the direction of nested classes and the visitor pattern, but that seems rather heavyweight in my attempt. Type erasure seems to be fighting me, hard. Could you show me an outline of what that would look like done correctly?

  • 1
    Probably depends on the type to some extent. Could you briefly describe what Event and its int/string members mean? For instance, is the Event type conceptually equivalent to having one Int and two Maybe Strings? – Ixrec May 10 '16 at 15:45
  • 1
    Is Java the language you want or you have to work with? – Thomas Junk May 10 '16 at 18:56
  • Pattern matching may be a future feature in Java 1x which is described in JEP 305. – tsh Apr 9 '18 at 3:16
10

The author of 'Functional Programming in Scala' gives a nice illustration of the best that can be achieved in Java in a type safe manner:

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

Essentially, it uses a Church-encoding of the cases to ensure that the compiler will complain if any are missing.

The details are not readily summarized and indeed are so well covered in the article that there's no point in reproducing them here (that's what hyperlinks are for right?).

  • Thanks, this is more or less what I was looking for. Visitor ended up working out reasonably well for this case, but I will likely use the Church example in the future. – closeparen May 11 '16 at 0:05
  • 2
    I think that's clever, but I'm not sure it's idiomatic – Brian Agnew May 12 '16 at 12:26
  • The idiomatic equivalent is double dispatch, where you pass one object to t.match with the three methods rather than passing three functions. ( the linked article confuses Visitor pattern with double dispatch - visitor is a pattern for abstracting the iteration over a network, not for selecting on patterns ) – Pete Kirkham Sep 19 '16 at 10:36
  • Double dispatch is one way of implementing the Visitor pattern. I've no idea what you mean by 'abstracting the iteration over a network' - that's not a phrase used in the GoF definition of Visitor. – NietzscheanAI Sep 19 '16 at 11:49
  • I'm struggling with this blog/article because the he starts with this in his 'bad' example: public static int depth(Tree t) where he uses chained if an instanceof when the 'right' way to do this in Java is to define an interface with the method: public int depth() and use polymorphism. It seems like a straw man. – JimmyJames Sep 19 '16 at 13:44
4

What is the idiomatic way to do this sort of thing in Java?

There isn't really such a thing, given that Java (the language) is fundamentally imperative.

If you can run on the JVM, but not restricted to the Java language, you could investigate Scala, which would achieve something like the above using pattern matching.

Otherwise I think you're reduced to manually matching your various cases and calling methods as appropriate, or perhaps defining subtypes of 'Event' and using polymorphism to invoke particular methods for each subtype.

  • I think your last paragraph hits the nail on the head. The idiomatic approach to this in Java is to use polymorphism. Have Event be an interface with an appropriate method, and Thing1, Thing2 and Thing3 be implementations of that interface. It splits the code by type rather than by function, but then that's basically the point of OOP, isn't it? – Jules May 10 '16 at 20:35
  • Java being imperative is immaterial. Java could (and should) integrate sum types, but it just doesn't. – gardenhead Sep 19 '16 at 23:26
1

Take a look at https://github.com/johnlcox/motif which is a Scala-like "pattern matching" library for Java 8.

Not nearly as nice as ML / Erlang / Haskell, but still looks much more declarative than most.

0

You could use an enum and interface, overriding the simulate method, like this:

interface Event {
  void simulate()
}

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

Let's say you have Event event. Then you can use it in one of two ways:

event.simulate();

or

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

But the constructor gets called automatically by the JVM, so to also store the parameters there you're going to have to add properties with accessors etc.

Alternatively, you could code up your events as constant strings and use the switch construct, in which case you'd do

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. But yes, there's nothing native that directly mimics pattern matching :(

  • I'm not sure why you're using an enum here rather than classes - what's the benefit of the enum? – Jules May 10 '16 at 20:38
  • Because then you can also use it in a switch statement, which makes it slightly more like pattern matching – jasiek.miko May 10 '16 at 20:52
  • added that in now. – jasiek.miko May 10 '16 at 21:09
0

Visitor pattern or its church encoding equivalent is the way to go. It is rather verbose in Java, but hopefully tools like Derive4J (an annotation processor I maintain) or Adt4J can generate the boilerplate. Using such a tool your example become:

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 generate the Events class that provide a fluent pattern matching syntax (with exhaustive check that all cases are handled).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.