Assume I have some library code like
open System
module Foo =
let Invoke1 (action : Func<string>) = ()
let Invoke2 (action : Func<int, string>) = ()
let Invoke3 (key : int, add : Func<int, string>, update: Func<int, string, string>) = ()
let Invoke4 (key : int) (add : Func<int, string>) (update: Func<int, string, string>) = ()
type Bar =
static member Invoke1 (action : Func<string>) = ()
static member Invoke2 (action : Func<int, string>) = ()
static member Invoke3 (key : int, add : Func<int, string>, update: Func<int, string, string>) = ()
static member Invoke4 (key : int) (add : Func<int, string>) (update: Func<int, string, string>) = ()
Now when these methods were invoked, there're actually behavior difference
First, the following three lines do not compile
Foo.Invoke1(fun () -> "")
Foo.Invoke2(fun a -> "")
Foo.Invoke3(5, (fun k -> ""), (fun k v -> v))
They all have the same compilation error
error FS0002: This function takes too many arguments, or is used in a context where a function is not expected
However, the following three lines compile just fine
Bar.Invoke1(fun () -> "")
Bar.Invoke2(fun a -> "")
Bar.Invoke3(5, (fun k -> ""), (fun k v -> v))
So somehow when static member of a type accepts System.Func, a corresponding F# lambda can be implicitly converted and accepted. But for let bindings of module, it does not work?
I also used ILSpy to look at the IL generated. For Foo.Invoke1 and Bar.Invoke1, they have the same signature in IL as
.method public static
string Invoke1 (
class [mscorlib]System.Func`1<string> action
) cil managed
So there's no difference on the method itself in IL. For the type, I saw
.class public auto ansi abstract sealed Library.Foo
.class public auto ansi serializable Library.Bar
So this somehow causes the difference? Anyway, how to explain the behavior difference between module and type?
Then I also found that the following does not compile
Bar.Invoke4 (5) (fun k -> "") (fun k v -> v)
It comes with an error
error FS0001: This expression was expected to have type int but here has type unit
The difference between Bar.Invoke3 and Bar.Invoke4 is that the former uses tuple form and the latter uses currying form. However, somehow the latter does not compile. In case you are curious
Foo.Invoke4 (5) (fun k -> "") (fun k v -> v)
does not compile either, it's just the error is different, it's the same as all other "Foo" errors:
error FS0002: This function takes too many arguments, or is used in a context where a function is not expected
Any idea on why Bar.Invoke3 works but Bar.Invoke4 does not?
It's really confusing that when implicit conversion from F# lambda to System.Func can happen and when it cannot happen. What are the explanations for the behaviors described above?
I found some earlier related questions, such as
- Passing F# function to IEnumerable.Where vs IEnumerable.All
- Conversion of lambda expressions to Func
But still could not find a clear explanation on the behaviors.
Any idea?
Note: I tried the above code with F# 4.0
Some context: I found such behaviors when I was trying to explore whether I can write a F# library where some methods takes functors as parameter. Can I write it in a way that it can be used from both F#/C# code. I remember I can use F# lambda with .Net methods that takes System.Func as parameter (e.g. ConcurrentDictonary's AddOrUpdate). So I thought if my functions are using System.Func, it can serve both. Is this a good/bad assumption?