Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
Interop of delegate style types between F# and other .Net languages is a pain point that results from a fundamental difference in how delegates are represented in the F# language. Typical .Net languages like C# see a delegate as taking 0 to N parameters and potentially returning a value. F# represents all exposed delegates as taking and returning a single value via the FSharpFunc<T,TResult> type. Multiple parameters are expressed by having the TResult type be yet another FSharpFunc<T,TResult>.
This creates a host of problems calling exposed F# delegate / function types
- Can’t use C# lambda expressions which take more than one parameter
- System.Func<> expressions of equivalent logical shape can’t be converted
- Method group conversions don’t work
The only step is to introduce a conversion step when calling into F# code. F# does provide a helper method in F# in the FSharpFunc.FromConverter method. But once again it’s only useful for delegates of one parameter, anything higher requires a more in depth conversion. For example here is the C# code to convert a 2 parameter delegate into the equivalent F# type.
public static FSharpFunc<T1, FSharpFunc<T2,TResult>> Create<T1, T2, TResult>(Func<T1,T2,TResult> func)
{
Converter<T1, FSharpFunc<T2, TResult>> conv = value1 =>
{
return Create<T2,TResult>(value2 => func(value1, value2));
};
return FSharpFunc<T1, FSharpFunc<T2, TResult>>.FromConverter(conv);
}
Not very pretty or intuitive because the code needs to recreate the nested style of F# func’s. This gets even more tedious and error prone as it gets past 2 parameters.
The simplest solution, as is true with most F# interop scenarios, is to leverage F# itself to define the interop / conversion layer. It already naturally creates the proper nesting structure so why spend type redoing the logic in C#. The logic can then be exposed as a set of factory and extension methods to make it easily usable from C#.
[<Extension>]
type public FSharpFuncUtil =
[<Extension>]
static member ToFSharpFunc<'a,'b> (func:System.Converter<'a,'b>) = fun x -> func.Invoke(x)
[<Extension>]
static member ToFSharpFunc<'a,'b> (func:System.Func<'a,'b>) = fun x -> func.Invoke(x)
[<Extension>]
static member ToFSharpFunc<'a,'b,'c> (func:System.Func<'a,'b,'c>) = fun x y -> func.Invoke(x,y)
[<Extension>]
static member ToFSharpFunc<'a,'b,'c,'d> (func:System.Func<'a,'b,'c,'d>) = fun x y z -> func.Invoke(x,y,z)
static member Create<'a,'b> (func:System.Func<'a,'b>) = FSharpFuncUtil.ToFSharpFunc func
static member Create<'a,'b,'c> (func:System.Func<'a,'b,'c>) = FSharpFuncUtil.ToFSharpFunc func
static member Create<'a,'b,'c,'d> (func:System.Func<'a,'b,'c,'d>) = FSharpFuncUtil.ToFSharpFunc func
Now I can convert instances of System.Func<> to the F# equivalent by simply calling .ToFSharpFunc().
var cmd = Command.NewSimpleCommand(
name,
flagsRaw,
func.ToFSharpFunc());
Much better.
Comments
Anonymous
August 02, 2010
That's nice, but for two things - I'm not sure I see the point generating F# functions in C# code - couldn't an F# method have one overload that takes System.Func, and another overload that takes an F# function? Second, to really use this you'd need to make a lot more overloads, as System.Func can accept up to sixteen input parameters.Anonymous
August 02, 2010
@Joel Unfortunately F# does not have universal support for overloading. In cases where it does I agree an overloaded solution is easiest. But Let bindings for example do not overload on a Module. So in cases like that I use F# style func's and then use the posted code to interop into it. For the second yes a 100% complete solution would have a lot more code for the many System.Func versions.Anonymous
August 02, 2010
Thank you for sharing this. I am currently struggling with an opposite problem: converting from F# lambda to C# Func<>. Let's say I want to interop with C# method that takes a Func<T> argument, and I want to send a F# lambda (fun x -> some_function_of(x)). There does not seem to be a way of doing this, does it?