2
votes

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

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?

1

1 Answers

4
votes

In F# type-directed conversions are applied only in invocations of type members (F# spec 8.13.7)