Accessing Data from an F# Discriminated Union Type in C#: A Simplified Approach

The code for basic validation returning a discriminated union type has been borrowed from the F# for Fun & Profit blog. However, accessing the values against Success and Failure (where failure is a string, and success is the request again) when consuming it in C# requires big and unpleasant casts. This demands a lot of typing and the need to type actual types, which should be inferred or available in metadata. Is there a more efficient way to do this? The goal is to determine how well C# and F# can work together.


Question:

My aim is to comprehend the level of compatibility between C# and F#. To achieve this, I have utilized a piece of code from the F# for Fun & Profit blog that executes fundamental validation and generates a
discriminated union type
.

type Result<'TSuccess,'TFailure> = 
    | Success of 'TSuccess
    | Failure of 'TFailure
type Request = {name:string; email:string}
let TestValidate input =
    if input.name = "" then Failure "Name must not be blank"
    else Success input

In C#, accessing the values against Success and Failure requires big nasty casts. This involves a lot of typing and requires actual types to be typed, which I would expect to be inferred or available in the metadata.

var req = new DannyTest.Request("Danny", "fsfs");
var res = FSharpLib.DannyTest.TestValidate(req);
if (res.IsSuccess)
{
    Console.WriteLine("Success");
    var result = ((DannyTest.Result.Success)res).Item;
    // Result is the Request (as returned for Success)
    Console.WriteLine(result.email);
    Console.WriteLine(result.name);
}
if (res.IsFailure)
{
    Console.WriteLine("Failure");
    var result = ((DannyTest.Result.Failure)res).Item;
    // Result is a string (as returned for Failure)
    Console.WriteLine(result);
}

Is there an alternative method for accomplishing this task? Despite the potential for a runtime error when casting manually, I am optimistic that there may be a way to reduce the duration of the types’ accessibility (

DannyTest.Result<DannyTest.Request, string>.Failure

). Is there a superior approach available?


Solution 1:

When it comes to working with
Discriminated Unions
in a language lacking pattern matching, the process is never simple. Nonetheless, since your

Result<'TSuccess, 'TFailure>

type is relatively simple, there should be an elegant solution for employing it in C#. In case the type were more complex, like an expression tree, I would suggest utilizing the Visitor pattern.

Several options have been suggested for accessing values directly and defining methods for simple DUs, including the approach described in Mauricio’s blog post. My preferred method is to define methods using a similar style as

Int32.TryParse

, which ensures that the pattern will be familiar to C# developers. In F#, this method looks like

TryGetXyz

.

open System.Runtime.InteropServices
type Result<'TSuccess,'TFailure> = 
    | Success of 'TSuccess
    | Failure of 'TFailure
type Result<'TSuccess, 'TFailure> with
  member x.TryGetSuccess([] success:byref<'TSuccess>) =
    match x with
    | Success value -> success <- value; true
    | _ -> false
  member x.TryGetFailure([] failure:byref<'TFailure>) =
    match x with
    | Failure value -> failure <- value; true
    | _ -> false

This enhancement introduces two extensions,

TryGetSuccess

and

TryGetFailure

, which yield

true

when the value corresponds to the case. Additionally, these extensions return all parameters of the discriminated union case by means of

out

. The usage of C# is simple and self-explanatory for those familiar with

TryParse

.

  int succ;
  string fail;
  if (res.TryGetSuccess(out succ)) {
    Console.WriteLine("Success: {0}", succ);
  }
  else if (res.TryGetFailure(out fail)) {
    Console.WriteLine("Failuere: {0}", fail);
  }

The most crucial advantage of using this pattern is its familiarity. If you choose to expose F# types to C# developers, it’s important to do so in a straightforward manner so that C# users don’t perceive them as non-standard in any way.

When used correctly, this feature provides you with reasonable assurances that you will only retrieve values that are genuinely obtainable when the DU corresponds to a particular scenario.


Solution 2:


One can implement switch pattern matching with C# 7.0, which is quite similar to F# match and provides an elegant solution.

var result = someFSharpClass.SomeFSharpResultReturningMethod()
switch (result)
{
    case var checkResult when checkResult.IsOk:
       HandleOk(checkResult.ResultValue);
       break;
    case var checkResult when checkResult.IsError:
       HandleError(checkResult.ErrorValue);
       break;
}

The upcoming release of C#
8.0
is imminent, and it will introduce switch expressions. While I have not had the chance to test it yet, it is my expectation that we will have the ability to perform a task similar to this.

var returnValue = result switch 
{
    var checkResult when checkResult.IsOk:     => HandleOk(checkResult.ResultValue),
    var checkResult when checkResult.IsError   => HandleError(checkResult.ErrorValue),
    _                                          => throw new UnknownResultException()
};

For additional information, refer to https://blogs.msdn.microsoft.com/dotnet/2018/11/12/building-c-8-0/.


Solution 3:


What do you think of this idea? It draws inspiration from @Mauricio Scheffer’s comment and also incorporates code from
fsharpx
related to CSharpCompat.

C#:

MyUnion u = CallIntoFSharpCode();
string s = u.Match(
  ifFoo: () => "Foo!",
  ifBar: (b) => $"Bar {b}!");

F#:

  type MyUnion =
    | Foo
    | Bar of int
  with
    member x.Match (ifFoo: System.Func<_>, ifBar: System.Func<_,_>) =
      match x with
      | Foo -> ifFoo.Invoke()
      | Bar b -> ifBar.Invoke(b)

The greatest advantage of this approach is that it eliminates the chance of encountering a runtime error. By doing so, you won’t have to create a faulty default case, and if the F# type undergoes modifications such as adding a case, the C# code will not compile.


Solution 4:

Employing C# type aliasing can make the process of referencing DU Types in a C# File less complicated.

using DanyTestResult = DannyTest.Result;

Structural Pattern Matching in C# 8.0 and beyond facilitates the accomplishment of the following task with ease.

switch (res) {
    case DanyTestResult.Success {Item: var req}:
        Console.WriteLine(req.email);
        Console.WriteLine(req.name);
        break;
    case DanyTestResult.Failure {Item: var msg}:
        Console.WriteLine("Failure");
        Console.WriteLine(msg);
        break;
}

The F# DU reference type can be used with this strategy without any alterations, making it the most straightforward approach.

In order to further simplify the C# syntax, the codegen for interop could incorporate a Deconstruct method if F# is utilized.

DanyTestResult.Success(var req)

In case your F# DU is structured, you can simply perform a pattern match on the Tag property without specifying the type.

{Tag:DanyTestResult.Tag.Success, SuccessValue:var req}

Frequently Asked Questions

Posted in Uncategorized