Compliance with F # type pattern in C # code

Assume that there are F# definitions:

 type Either<'a,'b> = | Left of 'a | Right of 'b let f (i : int) : Either<int, string> = if i > 0 then Left i else Right "nothing" 

The f function is used in C# code:

 var a = Library.f(5); 

How can the result of result a match the pattern for data constructors? Sort of:

 /* (if a is Left x) do something with x (if a is Right y) do something with y */ 
+4
source share
3 answers

Using F # discriminatory joins from C # is a little inelegant because of how they are compiled.

I think the best approach is to define some members (on the F # side) that will make it easier to use types from C #. There are several options, but I prefer to define the TryLeft and TryRight , which behave similarly to Int32.TryParse (and therefore they should be familiar to C # developers using your F # API):

 open System.Runtime.InteropServices type Either<'a,'b> = | Left of 'a | Right of 'b member x.TryLeft([<Out>] a:byref<'a>) = match x with Left v -> a <- v; true | _ -> false member x.TryRight([<Out>] b:byref<'b>) = match x with Right v -> b <- v; true | _ -> false 

Then you can use the type from C # as follows:

 int a; string s; if (v.TryLeft(out a)) Console.WriteLine("Number: {0}", a); else if (v.TryRight(out s)) Console.WriteLine("String: {0}", s); 

You lose some of the security of F # by doing this, but it was expected in a language without pattern matching. But it’s good that anyone familiar with .NET should be able to use the API implemented in F #.

Another alternative would be to define a Match member that accepts the delegates Func<'a> and Func<'b> and calls the right delegate with the value carried in the case of left / right. This is slightly better from a functional point of view, but it may be less obvious to C # callers.

+7
source

I would define the Match member that delegates performed in each scenario. In F # you would do it like this (but if you wanted, you could do something equivalent in the C # extension method):

 type Either<'a,'b> = | Left of 'a | Right of 'b with member this.Match<'t>(ifLeft:System.Func<'a,'t>, ifRight:System.Func<'b,'t>) = match this with | Left a -> ifLeft.Invoke a | Right b -> ifRight.Invoke b 

You should now be able to do something like this in C #:

 var result = a.Match(ifLeft: x => x + 1, ifRight: y => 2 * y); 
+6
source

From 3.0 spec :

8.5.4 Compiled union type form for use with other CLI languages

The compiled union type U has:

  • One UI CLI static getter property for each case of zeroing C. This property gets a singleton object that represents each such case.
  • One CLI-nested UC type for each case of non-empty union C. This type has instance properties Item1, Item2 .... for each case union field or a single property of an object if there is only one field. However, a compiled union type, which has only one case, does not have a nested type. Instead, the type of union itself plays the role of type.

  • One static U.NewC CLI method for each case of non-empty union C. This method creates an object for this case.

  • One U.IsC CLI instance property for each case C. This property returns true or false for the case.
  • One property of the CLI U.Tag instance for each case C. This property retrieves or evaluates an integer tag that matches the case.
  • If U has more than one case, it has one CLI-nested type U.Tags. The U.Tags type contains one integer literal for each case, with an increase starting from zero.

  • A compiled union type has the methods needed to implement its automatically generated interfaces, in addition to any user-defined properties or methods.

These methods and properties cannot be used directly from F #. However, these types have the user interface List.Empty, List.Cons, Option.None and Option.Some properties and / or methods.

A compiled union type cannot be used as a base type in another language CLI, since it has at least one collector-private constructor and no public constructors.

If you cannot change F # api using paragraphs 2 and 4 above, you can do it something like this:

FROM#

 class Program { static void Main(string[] args) { PrintToConsole("5"); PrintToConsole("test"); } static void PrintToConsole(string value) { var result = test.getResult(value); if (result.IsIntValue) Console.WriteLine("Is Int: " + ((test.DUForCSharp.IntValue)result).Item); else Console.WriteLine("Is Not Int: " + ((test.DUForCSharp.StringValue)result).Item); } } 

F #

 namespace Library1 module test = open System type DUForCSharp = | IntValue of int | StringValue of string let getResult x = match Int32.TryParse x with | true, value -> IntValue(value) | _ -> StringValue(x) 

This solution is convenient in the sense that it also handles DU cases for the set, creating a new property for each element in the tuple.

+1
source

All Articles