|
F# Manual
- Contents
F# from C# and other .NET Languages
This section describes how F# data and code can be directly accessed from C# and other .NET languages. This forms part of the specification of the F# language. In general values can be accessed simply by calling the available values as static methods or using them as static fields. The following example shows F# code being accessed from C#. Here is the F# code. The values in bold indicate the functions we're going to be calling from C#:
These types and values can be accessed from the following C# program. The code in bold show the values above being used from C#. The code in blue shows how to pass a callback from C# to F#.
Concrete Types (Record, Abstract and Discriminated Union Types). Named F# types can be referred to from other .NET languages simply by quoting the appropriate type name. Built-in types list and option use the names Microsoft.FSharp.List and Microsoft.FSharp.Option. Generic Types.Concrete F# types with type parameters must be given appropriate generic arguments if the F# code is compiled to use generics. Type Abbreviations. F# type abbreviations are eliminated, i.e. are not visible to other .NET languages. Tuple Types and Function Types. See below In general values can be accessed simply by calling the available values as static methods or using them as static fields. This documentation is under construction. F# data types are compiled to .NET classes with additional support to access the information carried by the data type. Sometimes additional subclasses are generated for discriminants, though these should not be used directly. Constructing values of F# discriminated unions. A static method is supported for each discriminant, e.g. using Microsoft.FSharp; List<int> x = List<int>.Cons(3,List<int>.Nil); Option<int> y = Option<int>.Some(3); Further examples are shown in the worked example above. Discriminating on F# discriminated unions from C# code The following section applies if the type has more than one discriminant. The support depends very marginally on when "null" is used by the F# compiler as a possible representation of value of the data type. This is because types where "null" is used as representation cannot support some instance members (they would result in a null pointer exception when used from C#). "null" will ONLY be used by the F# compiler in EXACTLY the following situations:
If "null" is NOT used as representation then a type will support
If "null" IS used as representation then a type will support
In the latter case discrimination by predicates can be performed by comparing values to "null", since the null value is then guaranteed to be used for the single nullary constructor of the type. Thus if the C# value x has type MyType and the F# definition of MyType is: type MyType = A of ... | B of ... | C of ... then the following C# code is valid: Console.WriteLine("{0}", x.IsA()); Console.WriteLine("{0}", x.IsB()); switch (x.Tag) { case MyType.tag_A: Console.WriteLine("A"); break; case MyType.tag_B: Console.WriteLine("B"); break; default: Console.WriteLine("Must be a C"); break; } The compiled form of tuple type names and tuple member names is as follows: Tuple<_,_>.Item1 Tuple<_,_>.Item2 Tuple<_,_,_>.Item1 Tuple<_,_,_>.Item2 Tuple<_,_,_>.Item3 Tuple<_,_,_,_>.Item1 Tuple<_,_,_,_>.Item2 Tuple<_,_,_,_>.Item3 Tuple<_,_,_,_>.Item4 ... Tuple<_,_,_,_,_,_,_>.Item1 ... Tuple<_,_,_,_,_,_,_>.Item7 The compiled forms for tuples of size > 7 are under-specified. The above names change slightly when targetting .NET 1.0 or 1.1, i.e. a non-Whidbey release, because we then can't use the arity of the generic type to disambiguate the various "Tuple" names. So the names become Tuple2, Tuple3 etc. to Tuple7. Data fields compile as properties (not fields) for example: type recd = { Name: string; Size: int } will support recd.Name (a property, not a field) recd.Size (a property, not a field) A mutable field naturally has a "set" property. The compiled form of function types is Microsoft.FSharp.FastFunc<A,B>. This will not change, though the details of the implementation of the Microsoft.FSharp.FastFunc class may be revised. FastFunc<A,B> is not a delegate type (which may be what some users expect). This option has been finally rejected for both interoperability and performance grounds. Invoking functions Function values can be invoked using the f.Invoke method. For function types accepting multiple arguments through iterated function types, e.g. int -> int -> int, the the optimized application static methods Microsoft.FSharp.FastFunc.InvokeFast2, Microsoft.FSharp.FastFunc.InvokeFast3 etc. may be used. Creating function values that accept one argument It is important to be able to create and use values of type FastFunc from C# in a fashion that is as easy and natural as possible. One option is to create function values by using subclasses of FastFunc<A,B> that override the "Invoke" method. However this is not the recommended way of creating such values, since it is then not possible to use C#'s anonymous delegate feature when creating delegates, and you will have to manually "capture" all variables your function code refers to. The recommended uniform way to create a FastFunc is to use an anonymous delegate. You simply create an appropriate .NET function-like delegate (e.g. System.Converter) and then call an overload of Microsoft.FSharp.FuncConvert.ToFastFunc. In particular, FuncConvert.ToFastFunc(...) supports the following overloads: FuncConvert.ToFastFunc(System.Converter<T,U> f) producing type FastFunc<T,U> FuncConvert.ToFastFunc(System.Action<T> f) producing type FastFunc<T,object> FuncConvert.ToFastFunc(System.Predicate<T> f) producing type FastFunc<T,bool> Additionally, there is an implicit conversion from System.Converter<T,U> to FastFunc<T,U>, and thus you can omit the call to FuncConvert.ToFastFunc() but ONLY when reating a delegate of type System.Converter<A,B> (for some A,B). For example, the following are equivalent: List.map<int,string> (FuncConvert.ToFastFunc ((Converter<int,string>) delegate(int x) { return x.ToString() + x.ToString(); }), myList); and List.map<int,string>((Converter<int,string>) delegate(int x) { return x.ToString() + x.ToString(); }, myList); Creating curried function values that accept multiple arguments The above techniques works well when creating F# function values that expect one argument. However the above can be awkward when creating F# function values that accept multiple values, whether by "currying" or by "tupling". Thus the F# library defines additional similar types in order to support additional conversions. In particular it defines delegate void System.Action<A1,A2>(A1 x, A2 y); delegate void System.Action<A1,A2,A3>(A1 x, A2 y,A3 z); delegate B System.Converter<A1,A2,A3,B>(A1 x, A2 y,A3 z); and Microsoft.FSharp.FuncConvert.ToFastFunc(...) method supports the following overloads that produce "curried" functions: ToFastFunc(System.Converter<A1,B> f) producing type 'A1 -> B', i.e. FastFunc<A1,B> ToFastFunc(System.Converter<A1,A2,B> f) producing 'A1 -> A2 -> B', i.e. FastFunc<A1,FastFunc<A2,B>> ToFastFunc(System.Converter<A1,A2,A3,B> f) producing 'A1 -> A2 -> A3 -> B', i.e. FastFunc<A1,FastFunc<A2,FastFunc<A3,B>> > ToFastFunc(System.Action<A1> f) producing 'A1 -> unit', i.e. FastFunc<A1,object> ToFastFunc(System.Action<A1,A2> f) producing 'A1 -> A2 -> unit', i.e. FastFunc<A1,FastFunc<A2,object>> ToFastFunc(System.Action<A1,A2,A3> f) producing 'A1 -> A2 -> A3 -> unit', i.e. FastFunc<A1,FastFunc<A2,FastFunc<A3,object>>> For example: using System; using Microsoft.FSharp; using Microsoft.FSharp.MLLib; List<int> myList = List.of_array(new int[] { 4, 5, 6 }); string joined = List.fold_right<int,string> (FuncConvert.ToFastFunc((Converter<int,string,string>) delegate(int i,string acc) { return i.ToString() + "-" + acc; }), myList, ""); Creating function values that accept multiple arguments as tuples To create F# function values that accept their arguments as tuples use Microsoft.FSharp.FuncConvert.ToTupledFunc. ToTupledFastFunc(System.Converter<A1,B> f) producing 'A1 -> B', i.e. FastFunc<A1,B> ToTupledFastFunc(System.Converter<A1,A2,B> f) producing 'A1 * A2 -> B', i.e. FastFunc<A1,FastFunc<A2,B>> ToTupledFastFunc(System.Converter<A1,A2,A3,B> f) producing 'A1 * A2 * A3 -> B', i.e. FastFunc<A1,FastFunc<A2,FastFunc<A3,B>> > ToTupledFastFunc(System.Action<A1> f) producing 'A1 -> unit', i.e. FastFunc<A1,object> ToTupledFastFunc(System.Action<A1,A2> f) producing 'A1 * A2 -> unit', i.e. FastFunc<A1,FastFunc<A2,object>> ToTupledFastFunc(System.Action<A1,A2,A3> f) producing 'A1 * A2 * A3 -> unit', i.e. FastFunc<A1,FastFunc<A2,FastFunc<A3,object>>> |