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?
A really nice way to do this with C# 7.0 is using switch pattern matching, it's allllmost like F# match:
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:
See https://blogs.msdn.microsoft.com/dotnet/2018/11/12/building-c-8-0/ for more info.
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.
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 defineTryGetXyz
methods that follow the same style ofInt32.TryParse
- this also guarantees that C# developers will be familiar with the pattern. The F# definition looks like this:This simply adds extensions
TryGetSuccess
andTryGetFailure
that returntrue
when the value matches the case and return (all) parameters of the discriminated union case viaout
parameters. The C# use is quite straightforward for anyone who has ever usedTryParse
: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.
Probably, one of the simplest ways to accomplish this is by creating a set of extension methods:
This article contains a good insight. Quote:
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.
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 typeNow I have the choice of piping through
toResultInterop
at the F# boundary or doing so within the C# code.At the F# boundary
After the interop in Csharp
Mauricio Scheffer did some excellent posts for C#/F# interop, and use of techniques, both with and without the core F# libraries (or Fsharpx libraries), in such a way as to be able to use the concepts (made simple in F#) in C#.
http://bugsquash.blogspot.co.uk/2012/03/algebraic-data-type-interop-f-c.html
http://bugsquash.blogspot.co.uk/2012/01/encoding-algebraic-data-types-in-c.html
Also this might be of use: How can I duplicate the F# discriminated union type in C#?