discriminated - f# to c# online




What is the simplest way to access data of an F# discriminated union type in C#? (4)

A really nice way to do this with C# 7.0 is using switch pattern matching, it's allllmost like F# match:

var someResult = someFSharpClass.SomeResultReturningMethod()

switch (validationResult)
{
    case var checkResult when checkResult.IsOk:
       HandleValidationSuccess(checkResult.ResultValue);
       break;
    case var checkResult when checkResult.IsError:
       HandleValidationError(checkResult.ErrorValue);
       break;
}

I'm trying to understand how well C# and F# can play together. I've taken some code from the F# for Fun & Profit blog which performs basic validation returning 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

When trying to consume this in C#; the only way I can find to access the values against Success and Failure (failure is a string, success is the request again) is with big nasty casts (which is a lot of typing, and requires typing actual types that 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<DannyTest.Request, string>.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<DannyTest.Request, string>.Failure)res).Item;
    // Result is a string (as returned for Failure)
    Console.WriteLine(result);
}

Is there a better way of doing this? Even if I have to manually cast (with the possibility of a runtime error), I would hope to at least shorten access to the types (DannyTest.Result<DannyTest.Request, string>.Failure). Is there a better way?


I had this same issue with the Result type. I created a new type of ResultInterop<'TSuccess, 'TFailure> and a helper method to hydrate the type

type ResultInterop<'TSuccess, 'TFailure> = {
    IsSuccess : bool
    Success : 'TSuccess
    Failure : 'TFailure
}

let toResultInterop result =
    match result with
    | Success s -> { IsSuccess=true; Success=s; Failure=Unchecked.defaultof<_> }
    | Failure f -> { IsSuccess=false; Success=Unchecked.defaultof<_>; Failure=f }

Now I have the choice of piping through toResultInterop at the F# boundary or doing so within the C# code.

At the F# boundary

module MyFSharpModule =
    let validate request = 
        if request.isValid then
            Success "Woot"
        else
            Failure "request not valid"

    let handleUpdateRequest request = 
        request
        |> validate
        |> toResultInterop

public string Get(Request request)
{
    var result = MyFSharpModule.handleUpdateRequest(request);
    if (result.IsSuccess)
        return result.Success;
    else
        throw new Exception(result.Failure);
}

After the interop in Csharp

module MyFSharpModule =
    let validate request = 
        if request.isValid then
            Success "Woot"
        else
            Failure "request not valid"

    let handleUpdateRequest request = request |> validate

public string Get(Request request)
{
    var response = MyFSharpModule.handleUpdateRequest(request);
    var result = Interop.toResultInterop(response);
    if (result.IsSuccess)
        return result.Success;
    else
        throw new Exception(result.Failure);
}


Probably, one of the simplest ways to accomplish this is by creating a set of extension methods:

public static Result<Request, string>.Success AsSuccess(this Result<Request, string> res) {
    return (Result<Request, string>.Success)res;
}

// And then use it
var successData = res.AsSuccess().Item;

This article contains a good insight. Quote:

The advantage of this approach is 2 fold:

  • Removes the need to explicitly name types in code and hence gets back the advantages of type inference;
  • I can now use . on any of the values and let Intellisense help me find the appropriate method to use;

The only downfall here is that changed interface would require refactoring the extension methods.

If there are too many such classes in your project(s), consider using tools like ReSharper as it looks not very difficult to set up a code generation for this.





functional-programming