Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does F# compile functions that can take multiple different parameter types into IL?

I know virtually nothing about F#. I don’t even know the syntax, so I can’t give examples.

It was mentioned in a comment thread that F# can declare functions that can take parameters of multiple possible types, for example a string or an integer. This would be similar to method overloads in C#:

public void Method(string str) { /* ... */ }
public void Method(int integer) { /* ... */ }

However, in CIL you cannot declare a delegate of this form. Each delegate must have a single, specific list of parameter types. Since functions in F# are first-class citizens, however, it would seem that you should be able to pass such a function around, and the only way to compile that into CIL is to use delegates.

So how does F# compile this into CIL?

like image 534
Timwi Avatar asked Sep 21 '10 03:09

Timwi


People also ask

What does F X -> Y mean?

f:x↦y means that f is a function which takes in a value x and gives out y.

What does F mean math?

more ... A special relationship where each input has a single output. It is often written as "f(x)" where x is the input value. Example: f(x) = x/2 ("f of x equals x divided by 2")

What does F say about f?

What Does f ' Say About f ? The first derivative of a function is an expression which tells us the slope of a tangent line to the curve at any instant. Because of this definition, the first derivative of a function tells us much about the function. If is positive, then must be increasing.


1 Answers

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 ints" or "only work on floats" or whatnot. Some ML flavors (F# is a relative of OCaml) do exactly that, where e.g. the + operator only works on ints, and a separate operator, usually named +., works on floats. 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.)

like image 133
Brian Avatar answered Nov 15 '22 10:11

Brian