26

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?

Danny Tuppeny
  • 40,147
  • 24
  • 151
  • 275

7 Answers7

24

Working with discriminated unions is never going to be as straightforward in a language that does not support pattern matching. However, your Result<'TSuccess, 'TFailure> type is simple enough that there should be some nice way to use it from C# (if the type was something more complicated, like an expression tree, then I would probably suggest to use the Visitor pattern).

Others already mentioned a few options - both how to access the values directly and how to define Match method (as described in Mauricio's blog post). My favourite method for simple DUs is to define TryGetXyz methods that follow the same style of Int32.TryParse - this also guarantees that C# developers will be familiar with the pattern. The F# definition looks like this:

open System.Runtime.InteropServices

type Result<'TSuccess,'TFailure> = 
    | Success of 'TSuccess
    | Failure of 'TFailure

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

This simply adds extensions TryGetSuccess and TryGetFailure that return true when the value matches the case and return (all) parameters of the discriminated union case via out parameters. The C# use is quite straightforward for anyone who has ever used 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);
  }

I think the familiarity of this pattern is the most important benefit. When you use F# and expose its type to C# developers, you should expose them in the most direct way (the C# users should not think that the types defined in F# are non-standard in any way).

Also, this gives you reasonable guarantees (when it is used correctly) that you will only access values that are actually available when the DU matches a specific case.

ildjarn
  • 62,044
  • 9
  • 127
  • 211
Tomas Petricek
  • 240,744
  • 19
  • 378
  • 553
  • 1
    Whilst obviously Tomas is the authority in this area, his answer is very old now and I believe my solution using switch expressions below is much more appropriate in 2018. I would go so far as requesting it be made the accepted answer if that seems fair? It could easily be missed down there and all the other solutions are painful! – Ryan Dec 18 '18 at 18:59
  • 2
    @Ryan - You're absolutely right :-) your solution for C# 7 is much nicer than this! – Tomas Petricek Dec 26 '18 at 20:47
9

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

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;
}

EDIT: C# 8.0 is around the corner and it is bringing switch expressions, so although I haven't tried it yet I am expecting we will be able to do something like this this:

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

See https://blogs.msdn.microsoft.com/dotnet/2018/11/12/building-c-8-0/ for more info.

Ryan
  • 2,109
  • 1
  • 15
  • 19
  • 1
    Thanks for this answer! Just tried [C#8 switch](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-8#switch-expressions) (although for a different type than `Result`), looks something like: `result switch { Result.Success value => HandleOk(value.Item), Result.Failure failure => HandleError(failure.Item), _ => throw new UnknownResultException() };` – David Tchepak Oct 17 '19 at 04:44
4

You can use C# type aliasing to simplify referencing the DU Types in a C# File.

using DanyTestResult = DannyTest.Result<DannyTest.Request, string>;

Since C# 8.0 and later have Structural pattern matching, it's easy to do the following:

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;
}

This strategy is the simplest as it works with reference type F# DU without modification.

The syntax C# syntax could be reduced more if F# added a Deconstruct method to the codegen for interop. DanyTestResult.Success(var req)

If your F# DU is a struct style, you just need to pattern match on the Tag property without the type. {Tag:DanyTestResult.Tag.Success, SuccessValue:var req}

jbtule
  • 31,383
  • 12
  • 95
  • 128
3

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);
}
Community
  • 1
  • 1
Reid Evans
  • 1,611
  • 15
  • 19
3

How about this? It's inspired by @Mauricio Scheffer's comment above and the CSharpCompat code in FSharpx.

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)

What I like best about this is that it removes the possibility of a runtime error. You no longer have a bogus default case to code, and when the F# type changes (e.g. adding a case) the C# code will fail to compile.

Mark
  • 51
  • 4
2

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.

Community
  • 1
  • 1
Be Brave Be Like Ukraine
  • 7,596
  • 3
  • 42
  • 66
0

I'm using the next methods to interop unions from F# library to C# host. This may add some execution time due to reflection usage and need to be checked, probably by unit tests, for handling right generic types for each union case.

  1. On F# side
type Command = 
     | First of FirstCommand
     | Second of SecondCommand * int

module Extentions =
    let private getFromUnionObj value =
        match value.GetType() with 
        | x when FSharpType.IsUnion x -> 
            let (_, objects) = FSharpValue.GetUnionFields(value, x)
            objects                        
        | _ -> failwithf "Can't parse union"

    let getFromUnion<'r> value =    
        let x = value |> getFromUnionObj
        (x.[0] :?> 'r)

    let getFromUnion2<'r1,'r2> value =    
        let x = value |> getFromUnionObj
        (x.[0] :?> 'r1, x.[1] :? 'r2)
  1. On C# side
        public static void Handle(Command command)
        {
            switch (command)
            {
                case var c when c.IsFirstCommand:
                    var data = Extentions.getFromUnion<FirstCommand>(change);
                    // Handler for case
                    break;
                case var c when c.IsSecondCommand:
                    var data2 = Extentions.getFromUnion2<SecondCommand, int>(change);
                    // Handler for case
                    break;
            }
        }
Ilya Reva
  • 1
  • 1