Reputation: 2896
Given the type
type T =
static member OverloadedMethod(p:int) = ()
static member OverloadedMethod(p:string) = ()
Let's suppose we want to create a generic function that resolves to the specific overload based on the type of the parameter. The most intuitive way would be
//Case 1
let inline call o = T.OverloadedMethod o //error
call 3
call "3"
but this, despite the inline definition, doesn't work and the compiler complains
Error FS0041 A unique overload for method 'OverloadedMethod' could not be determined based on type information prior to this program point. A type annotation may be needed. Candidates: static member T.OverloadedMethod : p:int -> unit, static member T.OverloadedMethod : p:string -> unit
We can achieve what we want though, for example using the "operator trick"
//Case 2
type T2 =
static member ($) (_:T2, p:int) = T.OverloadedMethod(p)
static member ($) (_:T2, p:string) = T.OverloadedMethod(p)
let inline call2 o = Unchecked.defaultof<T2> $ o
call2 3
call2 "3"
The F# compiler here does (apparently) some more work and doesn't simply fall back to the .NET resolution.
Yet, this looks ugly and implies code duplication. It sounds like Case 1 should be possible.
What technical reasons justify this behaviour? My guess is that there is some trade-off (perhaps with .NET interoperability), but couldn't find more information.
EDIT
From the posts I extract this as a reason:
"a trait call is an F# compiler feature, so there must be two different ways of writing a simple call and a trait call. Using the same syntax for both is not convenient because it might be confusing, some uses could arise where a simple call is compiled as a trait call accidentally".
Let's put the question in a different perspective:
Looking at the code, it really seems straightforward what the compiler should do:
1) call is an inline function, so defer compilation to the use site
2) call 3 is an use site, where the parameter is of type int. But T.OverloadedMethod(int) exists, so let's generate a call to it
3) call "3" like previous case with string in place of int
4) call 3.0 error as T.OverloadedMethod(float) doesn't exist
I would really like to see a code example where letting the compiler do this would be a problem that justifies requiring the developer to write the additional code for the trait call.
At the end of the day, isn't one of F# strengths "conciseness and intuitiveness"?
Here we are in presence of a case where it looks like it could be better.
Upvotes: 0
Views: 384
Reputation: 26174
The reason of that behavior is that T.OverloadedMethod o
in
let inline call o = T.OverloadedMethod o
is not a trait call. It's rather simple .NET overloading that must be solved at the call site, but since your function type doesn't imply which overload to solve it simply fails to compile, this functionality is desired.
If you want to "defer" the overload resolution you need to do a trait call, making the function inline is necessary but not sufficient:
let inline call (x:'U) : unit =
let inline call (_: ^T, x: ^I) = ((^T or ^I) : (static member OverloadedMethod: _ -> _) x)
call (Unchecked.defaultof<T>, x)
Using an operator saves you many keystrokes by inferring automatically these constraints but as you can see in your question, it requires to include a dummy parameter in the overloads.
Upvotes: 1
Reputation: 3634
The trade-offs stem from the fact that this it's a completely erased compiler trick. This means that:
call2
function, it can only see your two $
method overloads.call2
is gone at runtime, meaning you couldn't invoke it through reflection, for example.async
stacktraces a bit nicer.call2
comes from assembly B, you can't just replace assembly B with a new version of call2
; you have to recompile A against the new assembly. This could potentially be a backwards compatibility concern.A rather interesting benefit is that it can lead to drastic performance improvements in specialized cases: Why is this F# code so slow?. On the flip side, I'm sure there are circumstances where it could cause active harm, or just bloat the resulting IL code.
Upvotes: 1