Posted on • Originally published atfareez.info
Emulating Discriminated Unions in C# using Records
Discriminated Union / Sum Type is one of the common features found amoung strong typed functional languages like F#, Haskell etc. Ever since I started using them in F#, I can't stop wondering why such an useful feature is not available on the popular languages. C# has been adding a lot of functional features over the last few years. So, we are going to achieve something close to that in C# using Records and Inheritance.
In F#, we can create aDiscriminated Union forShape as shown below.
type Shape = Circle of int | Square of int | Rectangle of int * int | Triangle of int * int * int | Line of int
Discriminated Unions alone cannot be beneficial without powerfulPattern Matching constructs built into the language. In the case of F#, calculating area for any shape is easy as writing a function with Pattern Matching.
let getArea s = match s with | Circle(r) -> Math.PI * float(r) * float(r) | Square(x) -> float(x*x) | Rectangle(l, b) -> float(l*b) | Triangle(a, b, c) when a = b && b = c -> Math.Pow(float(a), 2.0) * Math.Sqrt(3.0)/4.0 | Triangle(a, b, c) when Math.Pow(float(c), 2.0) = Math.Pow(float(a), 2.0) + Math.Pow(float(b), 2.0) -> float(a * b) / 2.0 | _ -> 0.0
Now lets see how we can express this in terms of Records in C#.
Records are introduced in C# 9.0 which are special classes with value equality
and simpler syntax. It takes a constructor with parameters and all the parameters mentioned in the constructor are automatically promoted into Properties with{init; get;}
. Records are immutable and compared by values by default, just like any F# type.(However, there are some differences like deep equality is not guaranteed when reference types are used within Records which we are not going to give attention now.)
publicrecordShape{};publicrecordCircle(intRadius):Shape;publicrecordSquare(intSide):Shape;publicrecordRectangle(intWidth,intHeight):Shape;publicrecordTriangle(intA,intB,intC):Shape;publicrecordLine(intLength):Shape;
So here we create an emptyShape
record which is being inherited by all the other shapes but with different set of parameters. Inheritance is the only relationship among these types which enables us to pass different shape objects likeCircle
orSquare
in the place ofShape
. As I said earlier,Discriminated Unions are not useful on its own without Pattern Matching and C# has been adding good Pattern Matching support over last few versions. This allows us to compare type of the Record and evaluate an expression based on that.
So thegetArea
function would like the one below in C# 9.0.
staticdoubleArea(Shapes)=>sswitch{Circlec=>Math.PI*c.Radius*c.Radius,Squaresquare=>square.Side*square.Side,Rectangler=>r.Width*r.Height,Trianglet// Equilateral TrianglewhenMath.Pow(t.C,2)==Math.Pow(t.A,2)+Math.Pow(t.B,2)=>t.A*t.B/2,Trianglet// Right angled Trianglewhent.A==t.B&&t.B==t.C=>Math.Pow(t.A,2)*(Math.Sqrt(3)/4),Lineor_=>0,};
It is pretty close to the F# version of the code. Honestly I didn't expect C# would catch up with such good Pattern Matching so soon. But it's not quite common yet to see C# code similar to this as it will take time for the developers to adopt to the new way of thinking to solving problems.
Top comments(0)
For further actions, you may consider blocking this person and/orreporting abuse