Emulating Java Enums in .NET - F# Edition
I'm not usually one to follow up replies from another's blog in my own, but some challenges need further analysis. Ayende posted earlier about emulating the behavior of Java Enums in .NET. Since the inception of C#, there has been a lot of back and forth between Java and C# in terms of features such as generics, attributes (annotations), foreach statements, and lastly enums. There are significant differences between the two, but let's see if we can bridge that gap.
Enter Java Enums
Previous to Java 5.0, Java had a standard way of delcaring an enumerated type as a constant. This was neither type safe, brittle and rather uninformative (what does 3 mean anyways?). Finally, come 5.0, this feature was added to have simple enums such as we've had in C# all along. But, unlike C#, these were not capable of being cast to an integer, unsigned or otherwise. A simple enum could look something like this.
public enum PriorityLevel { Low, Medium, High }
Not only can they hold the value just as C/C++/C# enums can, they can also hold behavior and data. Let's expand our PriorityLevel to hold an integer level equivalent.
public final enum PriorityLevel { Low("Low Priority"), Medium("Medium Priority"), High("High Priority"); private static final Map<String,Status> lookup = getLookup(); private static HashMap<String, PriorityLevel> getLookup() { HashMap<String, PriorityLevel> l = new HashMap<String,PriorityLevel>(); for(PriorityLevel p : EnumSet.allOf(PriorityLevel.class)) l.put(p.getLevel(), p); return l; } private String level; private PriorityLevel(String level) { this.level = level; } public String getLevel() { return level; } public static PriorityLevel get(String level) { return lookup.get(level); } }
This gives us the ability to define a string equivalent for our given enum value. This can be a powerful concept that the data is not just limited to integers. So, this had me thinking about the possibilities of this in .NET.
Enter C# 3.0 and Extension Methods
Given that the ability of the Java enum has the ability to map itself to another data type's equivalent quite easily, it was a matter of time before we tried something like that in C#. With the birth of extension methods, we have the ability to add features onto given types, such as enums. An example of taking the above example and using extension methods might look like this.
{
Low,
Medium,
High
}
public static class Extensions
{
public static string GetLevel(this PriorityLevel level)
{
switch(level)
{
case PriorityLevel.High :
return "High Priority";
case PriorityLevel.Medium :
return "Medium Priority";
case PriorityLevel.Low :
return "Low Priority";
default :
throw new NotSupportedException();
}
}
}
It's not the best solution, but it certainly works. Instead, let's look at another language, and yes, one that I've been talking a bit about recently in regards to F#.
Enter F#
When I was looking over Ayende's post, it struck me that people were using Java enums in such a way that it looked like discriminated unions. This is the ability to create a set of discriminators to use with pattern matching. Each discriminator may hold different data types as well, which makes them extra powerful. Let's look at an equivalent of the above examples in F#.
type PriorityLevel =
| Low
| Medium
| High
with member x.GetLevel() =
match x with
| Low -> "Low Priority"
| Medium -> "Medium Priority"
| High -> "High Priority"
This is just a simple example of being able to add on behaviors to our discriminated union. It could be expanded on to include more behaviors, but I think the point is well made.
The Challenge
Back to the challenge at hand, Ayende posted a quick code snippet of Java enum code that he wanted to transform into .NET. Let's first look at the Java code and then the F# code sample that I put as an equivalent.
private static enum Layer { FIRST, SECOND; public boolean isRightLayer(WorkType type) { if (this == FIRST && type != WorkType.COLLECTION) return true; return this == SECOND && type == WorkType.COLLECTION; } }
Now using the techniques I used above, let's give a more complete code sample of what that might look like using F#. I'll use discriminated unions and pattern matching once again to get the message across.
type WorkType =
| Collection
| NonCollection
type Layer =
| First
| Second
with member x.IsRightLayer(workType) =
match (x, workType) with
| l, w when l = First && w <> Collection -> true
| l, w -> l = Second && w = Collection
I was able to pattern match against not just the discriminator, but also the WorkType discriminated union as a tuple, so that they are treated as one. From there, I can pattern match against it rather easily. Any questions?
Wrapping It Up
Java enums are definitely powerful ways of holding and describing data, but as you can see, discriminated unions using F# and pattern matching can do a pretty adequate job as well to express the data. Using these concepts can get you most of the way there with replicating the behavior, but it's not a 100% solution by any means. But, what I hope it does do is get you to look beyond C# to the polyglot lifestyle. Now back to your regular programming.