Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
374 views
in Technique[技术] by (71.8m points)

.net - What is the simplest way to access data of an F# discriminated union type in C#?

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?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...