Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

F# Discriminated Union usage from C# [duplicate]

Tags:

c#

f#

c#-to-f#

What are the best ways to use F# Discriminated Unions from C#?

I have been digging into this problem for a while, I have probably found the simplest way, but as it is rather complex, there may be some other thing I don't see...

Having a discriminated union, e.g.:

type Shape =
    | Rectangle of float * float
    | Circle of float

the usage from C# I found would be (avoiding using vars, to make the type obvious):

Shape circle = Shape.NewCircle(5.0);
if (circle.IsCircle)
{
    Shape.Circle c = (Shape.Circle)circle;
    double radius = c.Item;
}

In C#, the NewXXXX static methods always create object of the Shape class, there is also a method IsXXXX to check if the object is of the type; if and only if yes, it is castable to the Shape.XXXX class, and only then its items are accessible; constructor of the Shape.XXXX classes are internal, i.e. unaccessible.

Is anyone aware of a simpler option to get the data from a discriminated union?

like image 586
Tomas Pastircak Avatar asked May 24 '14 08:05

Tomas Pastircak


3 Answers

If you are writing a library in F# that is exposed to C# developers, then C# developers should be able to use it without knowing anything about F# (and without knowing that it was written in F#). This is also recommended by F# design guidelines.

For discriminated unions, this is tricky, because they follow different design principles than C#. So, I would probably hide all processing functionality (like calculating area) in the F# code and expose it as ordinary members.

If you really need to expose the two cases to C# developers, then I think something like this is a decent option for a simple discriminated union:

type Shape =
    | Rectangle of float * float
    | Circle of float
    member x.TryRectangle(width:float byref, height:float byref) =
      match x with
      | Rectangle(w, h) -> width <- w; height <- h; true
      | _ -> false
    member x.TryCircle(radius:float byref) =
      match x with
      | Circle(r) -> radius <- r; true
      | _ -> false

In C#, you can use it in the same way as the familiar TryParse methods:

int w, h, r;
if (shape.TryRectangle(out w, out h)) { 
  // Code for rectangle
} else if (shape.TryCircle(out r)) {
  // Code for circle
}
like image 200
Tomas Petricek Avatar answered Sep 19 '22 17:09

Tomas Petricek


According to the F# spec, the only interop available is through the following instance methods

  • .IsC...

  • .Tag (which gives an integer tag to each case)

  • .Item (on the subtypes to get the data - this is only present when there is more than one union case)

However, you are free to write methods in the F# to make the interop easier.

like image 38
John Palmer Avatar answered Sep 19 '22 17:09

John Palmer


Assuming that we need to calculate the area of each Shape polymorphically.

In C# we would normally create a hypothetical object hierarchy and a Visitor. In this example, we would have to create a ShapeVisitor class and then a derived ShapeAreaCalculator visitor class.

In F#, we can use Pattern Matching on the Shape type:

let rectangle = Rectangle(1.3, 10.0)
let circle = Circle (1.0)

let calculateArea shape =
    match shape with
    | Circle radius -> 3.141592654 * radius * radius
    | Rectangle (height, width) -> height * width

let rectangleArea = calculateArea(rectangle)
// -> 1.3 * 10.0

let circleArea = calculateArea(circle)
// -> 3.141592654 * 1.0 * 1.0
like image 40
Nikos Baxevanis Avatar answered Sep 17 '22 17:09

Nikos Baxevanis