Tuesday, July 1, 2008

The Enum is perfect... well almost.

Ok so its time to admit to something. I'm deeply in love with Java's Enum! In my opinion it was the best part of Java 5 and I still wonder why it took Sun 10 years to add this powerful static modeling construct.

The power of the Enum
Although there certainly are known limitations of the Enum, i.e. how you can't extend it, I have never run into a practical limitation myself. Until now that is. The issue I want to raise is that of Enum forward referencing.

The great Alan Turing taught us that at the end of the day, everything can be modeled by a Turing Machine and finite automata. We may not often consciously operate at this level, but many things still makes sense to model this way being it a regular expression matcher, navigation rules or similar. The Java Enum appears to be a perfectly simple, fast and type-safe way of modeling this... or does it?

State machine with an Enum
Since the Java Enum effectively is just a group of static class instances, it allows us to attach methods and state to each static instance. We should be able to use this fact to express a transitive relationship with outself. To model a CD player, we'd write something like this:



public enum PlayerState {
STOPPED(PlayerState.PLAYING),
PLAYING(PlayerState.STOPPED, PlayerState.PAUSED),
PAUSED(PlayerState.PLAYING, PlayerState.STOPPED);

private final EnumSet<PlayerState> transitions;

PlayerState(final PlayerState... transitions){
this.transitions = EnumSet.copyOf( Arrays.asList(transitions));
}

// State transition and query logic below...
}


Except that won't compile! The compiler complains with an "illegal forward reference" error. I can understand why it's the easiest to simply not allow this circular use case, but considering everything else in Java allows forward referencing (i.e. we don't need header files as in C++) it would've been nice if that was the case with Enum's too.
The JLS simply states:
"It is a compile-time error for the constructors, instance initializer blocks, or instance variable initializer expressions of an enum constant e to refer to itself or to an enum constant of the same type that is declared to the right of e"
Granted, it has been some years since my last compiler course but I can't see any reason not to allow forward referencing in this particular case where everything is known. I suspect the culprit simply being that Enum, as with so many other things, was added later using only existing features such as not to change existing initialization rules of the JVM.

11 comments:

Anonymous said...

See Joshua Bloch's talk "Effective Java Reloaded" from JavaOne or Google IO from this year on slide 26 and 27 for a possible solution to this.

public enum Phase {
SOLID, LIQUID, GAS;
public enum Transition {
MELT(SOLID, LIQUID), FREEZE(LIQUID, SOLID),
BOIL(LIQUID, GAS), CONDENSE(GAS, LIQUID),
SUBLIME(SOLID, GAS), DEPOSIT(GAS, SOLID);
private final Phase src;
private final Phase dst;
Transition(Phase src, Phase dst) {
this.src = src;
this.dst = dst;
}
(... some initialization cut out....)
}
}

Casper Bang said...

Thanks for the tip Anonymous, guess I better get Bloch's latest edition.

Not 100% sure how it helps my case though, it's not at all clear from his presentation how client code of this workaround looks like and it seems to focus an awfully lot on explicit transitions rather than having these be modeled implicit by a state relationship.

You are required to uniquely name each transition. In Bloch's example he has the advantage of having naturally unique transition names even if they go to the same state (i.e. going to a liquid is either by melting or condensing). However many thing you may wish to model does not lend itself to such rich prose. In my example, how would you name the two transitions going to stop? I suppose you could call them STOP_FROM_PLAYING and STOP_FROM_PAUSED but I can't imagine this naming scheme scale and read very well in real application scenarios.

יוסי said...

Just tried that on Eclipse. Seems to compile without a glitch.

Casper Bang said...

> Just tried that on Eclipse. Seems > to compile without a glitch.

Interesting. I suppose the Eclipse compiler always were a bit more lenient. It does not work on the Sun based compilers however.

Anonymous said...

Cannot reference a field before it is defined, said Eclipse (Galileo) to me...

Aria said...

I've just begun learning about Enum types in the last few days and I can see why the forward referencing is not permitted.

The reason is constant Enum types are initialized in the static block [of the particular Enum type by the compiler-generated code]. In other word, they call upon either a user-defined constructor or a synthetically created one by the compiler. Therefore, the compiler cannot certainly determine whether the initialization has been thoroughly performed until the static block is completed. At the same time, as I said, the instantiations are performed in the same static block. It is obvious that the compiler cannot use an Enum constant type when it hasn't been initialized--hence the compile-time error.

P.S. Compiler allows a constant field that its value is known at compile-time, for instance a constant, in the constructors or field variables. i.e.

enum EnumT {
DOO, DAA;

static z = 7;
static final y = 9;

EnumT() {
int i = y; // OK
int k = z; // Compile-time error
}
}

Casper Bang said...

Thanks for the feedback Aria, static initialization rules of Java aside - it would be a really handy feature to be able to do type-safe state-machine modelling with an enum. Afterall, it's perfectly possible in other languages, like i.e. Fantom (which also runs on the JVM).

I dived a little deeper into the issue btw. in a later entry: http://coffeecokeandcode.blogspot.com/2008/12/java-enum-relational-modelling.html

glenner003 said...

Hi Casper,

I was searching for a workaround for the exact same issue.

eventually I resolved it with

public enum PlayerState {
STOPPED(),
PLAYING(),
PAUSED();

static {
STOPPED.setTransitions(PlayerState.PLAYING);
PLAYING.setTransitions(PlayerState.STOPPED, PlayerState.PAUSED);
PAUSED.setTransitions(PlayerState.PLAYING, PlayerState.STOPPED);
}

private final EnumSet transitions;

PlayerState(){}

private void setTransitions(final PlayerState... transitions){
this.transitions = EnumSet.copyOf( Arrays.asList(transitions));
}

// State transition and query logic below...
}

Casper Bang said...

Nice idea glenner003. While I was not able to make your idiom work exactly as you wrote it (had to remove final modifier for transitions field), relaxing the immutable requirement does indeed work.

Fabio Veronez said...

What about using an abstract method?


public enum PlayerState {
STOPPED {
@Override
protected Collection getTransitions() {
return EnumSet.of(PlayerState.PLAYING);
}
},

PLAYING {
@Override
protected Collection getTransitions() {
return EnumSet.of(PlayerState.STOPPED, PlayerState.PAUSED);
}
},

PAUSED {
@Override
protected Collection getTransitions() {
return EnumSet.of(PlayerState.PLAYING, PlayerState.STOPPED);
}
};

protected abstract Collection getTransitions();
}

Casper Bang said...

Thanks for the feedback Fabio, that's an excellent pattern and workaround!