4
votes

In a c# dll I have a method, that takes func parameters:

public static AttrDiffRule Create<T>(string a_attr, string b_attr, Func<IAttrProxy,IAttrProxy,T,bool> parametricRule, T ruleParam, string desc = null)

and some predefined default methods intended for it:

public static bool NumberEqualsWithTolerance(IAttrProxy a, IAttrProxy b, double tolerance)

Now when using this in C#, I can write the following and it works:

var tmp = DefaultRules.Create("fds", "fds", DefaultRules.NumberEqualsWithTolerance, 10.0);

But, in F# this:

let attrRule = DefaultRules.Create("fd","fdsa", DefaultRules.NumberEqualsWithTolerance, 89.)

gives syntax error: "Error FS0002 This function takes too many arguments, or is used in a context where a function is not expected"

What would be the correct way to pass a C# static method into a parameter expecting a Func<> in F#?

It is important to actually pass in the function, and not a lambda wrapper, because the Create method's job is to use the argument function's MethodInfo, which gets hidden by the lambda wrapper's one.

The passed in function does not have overloads, also tried with specifying the type in place like

(DefaultRules.NumberEqualsWithTolerance : Func<IAttrProxy,IAttrProxy,float,bool>)
2

2 Answers

2
votes

This is a case of F# being very thoughtful on your behalf - by helping you write more idiomatic F#.

In .NET, you are not actually passing in the function, as if it's a member reference, rather you are passing in a delegate object of type Func<>. The construction of the delegate object is done implicitly by C# when it has the necessary type information.

We can see this more clearly if we refactor this into an actual delegate type:

public delegate bool ParametricRule<T>(IAttrProxy a, IAttrProxy b, T value);

public static AttrDiffRule Create<T>(string a_attr, string b_attr, ParametricRule<T> parametricRule, T ruleParam, string desc = null)
{
    return default;
}

If you try to construct a ParametricRule in F#, you'll see that its type is:

ParametricRule(IAttrProxy -> IAttrProxy -> 'a -> bool)

The rationale is that this way you can use regular F# functions, instead of some un-F#ish tupled input function. And this why it doesn't work in your case. Because you're trying to throw the tupled version from C# right back at it.

So if you refactor your C# implementation to:

protected static bool NumberEqualsWithToleranceImpl(IAttrProxy a, IAttrProxy b, float tolerance)
{
    return default;
}

public static ParametricRule<float> NumberEqualsWithTolerance => NumberEqualsWithToleranceImpl;

you'll see that it works like you'd expect it to, both from F# and C#.

let attrRule = DefaultRules.Create("fd","fdsa", DefaultRules.NumberEqualsWithTolerance, 89.0f) //compiles, yay!
1
votes

Sometimes the type resolution has trouble when passing a method as a function parameter, because there can be overloads on the method that make the signature ambiguous. You can just wrap the function in a lambda that passes the parameters.

let attrRule = 
    DefaultRules.Create(
        "fd",
        "fdsa", 
        (fun a b tolerance -> DefaultRules.NumberEqualsWithTolerance(a, b, tolerance)), 
        89.0)