We came across an issue recently with F# code calling into C# code. I have simplified the issue down as simple as I can. The C# code is as follows:
using System;
namespace CSharpLib
{
public abstract class InputMessageParent
{
public readonly Guid Id;
protected InputMessageParent(Guid id) { this.Id = id; }
}
public class Result { public string ResultString; }
public enum FunctionResult
{
None,
Good,
Bad
}
public class ConfigurationBuilder
{
public Result DoWork<TMessage>(
string param1,
Func<TMessage, FunctionResult> action)
where TMessage : InputMessageParent
{
return new Result() { ResultString = "Good" };
}
}
}
The F# code needs to call the ConfigurationBuilders DoWork function. Note that the DoWork function takes two parameters, a simple string and a Func as a second parameter. The Func takes in a TMessage which must inherit from InputMessageParent and it returns a simple Result type. The F# code is as follows:
open System
type InputMessageConcreteTypeA =
inherit CSharpLib.InputMessageParent
val Property1:string
new (id, property1Value) =
{
inherit CSharpLib.InputMessageParent(id)
Property1 = property1Value
}
[<EntryPoint>]
let main argv =
let actionImpl (input:InputMessageConcreteTypeA) =
CSharpLib.FunctionResult.Good
let builder = new CSharpLib.ConfigurationBuilder()
builder.DoWork("param1", actionImpl) |> ignore
0
This code does not compile. The actionImpl type is InputMessageConcreteTypeA -> CSharpLib.FunctionResult, this is exactly the type of the second parameter that DoWork is expecting but instead it gives me the following error: This expression was expected to have type 'Func<'a,CSharpLib.FunctionResult>' but here has type 'b -> CSharpLib.FunctionResult'
Interestingly, if I change the code to the following its does compile:
[<EntryPoint>]
let main argv =
let actionImpl (input:InputMessageConcreteTypeA) =
CSharpLib.FunctionResult.Good
let builder = new CSharpLib.ConfigurationBuilder()
builder.DoWork("param1", fun input -> actionImpl(input)) |> ignore
0
Why does the code compile for the inline anonymous function which is the exact same type definition as actionImpl but wont compile if I just pass actionImpl directly? The inline anonymous function looks pretty pointless but is the only solution that we have at the moment. Is there a better way?
The F# function type 'a -> 'b
is not, in fact, the same as the C# type Func<a,b>
.
The reason for why they're different is a bit moot, but the upshot is that you can't just willy-nilly pass one type off as the other. Type mismatch. This is what the compiler is telling you: expected to have type Func<...>, but here has type 'b -> ...
For lambda expressions, however, the compiler makes an exception. Normally, a lambda expression fun x -> e
will have type 'a -> 'b
(where x:'a
and e:'b
), but in cases where it's already known from the context that the expected type is Func<_,_>
the compiler will oblige and compile the lambda expression as a Func
.
This exception was made to ease interop with .NET libraries that make heavy use of lambda expressions, such as LINQ. But this exception doesn't apply to referencing named functions by their name. It probably should apply, but it doesn't.
If you don't want to have a pointless lambda expression, the only other way to go is to explicitly create an object of type Func<_,_>
by calling its constructor and passing the function to it, then pass that object to DoWork
:
builder.DoWork("param1", Func<_,_> actionImpl)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With