Udostępnij za pośrednictwem


Converting System.Func to FSharpFunc

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?