I am new to F#, and I was surprised to find that the type of f x y = x + y
is actually int -> int -> int
. Appearently, this is due to some performance trade-off.
But why is this actually necessary? Why not just infer the type to be 'a -> 'a -> 'a
or something similar? It seems to work for comparison: the type of g x y = x < y
is x:'a -> y:'a -> bool when 'a : comparison
. Why not for arithmetic operators as well?
Couldn't the compiler statically infer the specific primitive types from the call sites and specialize the generic function from there, falling back to some dynamic dispatch if this fails?
This might very well be obvious, but I could not find any good resources on this. What is the reasoning behind this behavior?
Yes, for those operators int
is the default type inferred unless you specify a different one or is inferred by the use. If you want to define them for all types you have to make the function inline:
let inline f x y = x + y
But notice that the signature is:
x: ^a -> y: ^b -> ^c
when ( ^a or ^b) : (static member ( + ) : ^a * ^b -> ^c)
This is because in .NET you can't use member constraints but F# resolves them at compile time. That's why you see those 'hat types' and the constraint that those type should have a static member (+) defined.
Also notice the type variables are not a -> a -> a
as you suggest, that's because in the .NET framework not all addition operations respect that signature. Things are different in other environments like Haskell, there the addition is strictly a -> a -> a
but in .NET you can add for instance a TimeSpan to a DateTime:
System.DateTime(2000,1,1) + new System.TimeSpan(1, 2, 0, 30, 0)
and the result it's a DateTime, here the signature is: a -> b -> a
Comparison is a different story since that constraint actually exists at .NET level so it can be compiled and encoded in the IL whereas member constraints need to be resolved at compile time, that why the function has to be marked as inline.
I think you misinterpreted the explanation in the linked question: this is not due a performance trade-off, the real reason is a .NET type system limitation. The fact that an inline function executes faster in most cases (since it's inlined by the compiler) is a secondary effect.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With