This question is a little ambiguous, so I'll just ramble about what's true of F#.
In F#, methods can be overloaded, just like C#. Methods are always accessed by a qualified name of the form someObj.MethodName
or someType.MethodName
. There must be context which can statically resolve the overload at compile-time, just as in C#. Examples:
type T() =
member this.M(x:int) = ()
member this.M(x:string) = ()
let t = new T()
// these are all ok, just like C#
t.M(3)
t.M("foo")
let f : int -> unit = t.M
let g : string-> unit = t.M
// this fails, just like C#
let h = t.M // A unique overload for method 'M' could not be determined
// based on type information prior to this program point.
In F#, let-bound function values cannot be overloaded. So:
let foo(x:int) = ()
let foo(x:string) = () // Duplicate definition of value 'foo'
This means you can never have an "unqualified" identifier foo
that has overloaded meaning. Each such name has a single unambiguous type.
Finally, the crazy case which is probably the one that prompts the question. F# can define inline
functions which have "static member constraints" which can be bound to e.g. "all types T
that have a member property named Bar
" or whatnot. This kind of genericity cannot be encoded into CIL. Which is why the functions that leverage this feature must be inline
, so that at each call site, the code specific-to-the-type-used-at-that-callsite is generated inline.
let inline crazy(x) = x.Qux(3) // elided: type syntax to constrain x to
// require a Qux member that can take an int
// suppose unrelated types U and V have such a Qux method
let u = new U()
crazy(u) // is expanded here into "u.Qux(3)" and then compiled
let v = new V()
crazy(v) // is expanded here into "v.Qux(3)" and then compiled
So this stuff is all handled by the compiler, and by the time we need to generate code, once again, we've statically resolved which specific type we're using at this callsite. The "type" of crazy
is not a type that can be expressed in CIL, the F# type system just checks each callsite to ensure the necessary conditions are met and inlines the code into that callsite, a lot like how C++ templates work.
(The main purpose/justification for the crazy stuff is for overloaded math operators. Without the inline
feature, the +
operator, for instance, being a let-bound function type, could either "only work on int
s" or "only work on float
s" or whatnot. Some ML flavors (F# is a relative of OCaml) do exactly that, where e.g. the +
operator only works on int
s, and a separate operator, usually named +.
, works on float
s. Whereas in F#, +
is an inline
function defined in the F# library that works on any type with a +
operator member or any of the primitive numeric types. Inlining can also have some potential run-time performance benefits, which is also appealing for some math-y/computational domains.)