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?
5 Answers 5
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– closeparen2016年05月11日 00:05:08 +00:00Commented May 11, 2016 at 0:05
-
2I think that's clever, but I'm not sure it's idiomaticBrian Agnew– Brian Agnew2016年05月12日 12:26:24 +00:00Commented May 12, 2016 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– Pete Kirkham2016年09月19日 10:36:17 +00:00Commented Sep 19, 2016 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– NietzscheanAI2016年09月19日 11:49:15 +00:00Commented Sep 19, 2016 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– JimmyJames2016年09月19日 13:44:06 +00:00Commented Sep 19, 2016 at 13:44
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– Jules2016年05月10日 20:35:10 +00:00Commented May 10, 2016 at 20:35
-
Java being imperative is immaterial. Java could (and should) integrate sum types, but it just doesn't.gardenhead– gardenhead2016年09月19日 23:26:57 +00:00Commented Sep 19, 2016 at 23:26
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.
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– Jules2016年05月10日 20:38:11 +00:00Commented May 10, 2016 at 20:38
-
Because then you can also use it in a switch statement, which makes it slightly more like pattern matchingjasiek.miko– jasiek.miko2016年05月10日 20:52:23 +00:00Commented May 10, 2016 at 20:52
-
added that in now.jasiek.miko– jasiek.miko2016年05月10日 21:09:47 +00:00Commented May 10, 2016 at 21:09
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).
-
Could you clarify your affiliation with the project in your post (see, stackoverflow.com/help/promotion).Benni– Benni2016年09月17日 20:39:48 +00:00Commented Sep 17, 2016 at 20:39
Explore related questions
See similar questions with these tags.
Event
type conceptually equivalent to having oneInt
and twoMaybe Strings
?