Discriminated Unions in C# Mono Compiler
Recently I’ve been using F# a bit. F# is .NET’s functional language (the syntax of F# 1.0 was backward compatible with OCaml, but 2.0 has diverged enough to make it more distinct). Learning F# was a huge mind-shift from the C-family of languages. Of all the features of F#, like implicit typing, tail recursion, and monads, many people list discriminated unions as their favorite.
Discriminated unions feel like C# enums on the surface. For instance, a union that can represent states of a light switch:
type LightSwitch =
| On
| Off
// And to use it, we use pattern matching:
let lightSwitch = getLightSwitchState()
match lightSwitch with
| On ->
turnOnLight()
| Off ->
turnOffLight()
This example is really no different from C# enums. Discriminated unions, however, can hold data. For instance, consider when our light switch needs to also be a dimmer:
type LightSwith =
| On
| Dimmed of int
| Off
// And to use it, we use pattern matching:
let lightSwitch = getLightSwitchState()
match lightSwitch with
| On ->
turnOnLight()
| Dimmed intensity -> dimLightToIntensity intensity
| Off ->
turnOffLight()
In C# we would have had to rewrite this whole program to handle the new dimmer requirement. Instead, we can just tack on a new state that holds data.
When you’re deep in the F# mindset, this structure makes perfect sense. But try implementing a discriminated union in C#. There’s the enum-like part, but there’s also the part that holds different sizes of data. There’s a great stackoverflow answer that explains how the F# compiler handles discriminated unions internally. It requires 1 enum, 1 abstract class and n concrete implementations of the abstract class. It’s quite over-complicated to use in every-day C#.
Nevertheless, I really want to use discriminated unions in my C# code because of how easy they make state machines & workflows. I’ve been brainstorming how to do this. There are several implementations as C# 3.5 libraries, but they’re cumbersome to use. I’ve been looking at the source code for the mono C# compiler, and I think I want to go the route of forking the compiler for a proof-of-concept.
I’m debating what the syntax should be. I figure that the change would be easier if I re-used existing constructs and just tweaked them to work with the new concepts.
public enum LightSwith
{
On,
Dimmed(int intensity),
Off
}
// And to use
var value = GetLightSwitchValue();
switch(value)
{
case On:
TurnOnLight();
break;
case Dimmed(intensity):
DimLightToIntensity(intensity);
break;
case Off:
TurnOffLight();
break;
}
I’ve been debating if the Dimmed case should retain the regular case syntax or get a lambda-like syntax:
var value = GetLightSwitchValue();
switch(value)
{
case On:
TurnOnLight();
break;
case Dimmed(intensity) =>
{
DimLightToIntensity(intensity)
}
case Off:
TurnOffLight();
break;
}
I’m leaning toward the lambda syntax due to how C# usually handles variable scope. I’ve barely just cloned the mono repository and started reading the design documents to orient myself with the compiler. This could be a huge project, so I’m not sure how far I’ll actually get. But this is a very interesting idea that I want to try hashing out.