I’m still trying to wrap my head around how F# generalizes (or not) functions and types, and there’s a case that’s bugging me:
let min(a, b) = if a < b then a else b
let add(a, b) = a + b
let minInt = min(3, 4)
let minFloat = min(3.0, 4.0) // works!
let addInt = add(3, 5)
let addFloat = add(3.0, 5.0) // error: This expression was expected to have type
// int but here has type float
Here min has the generic type 'a * 'a -> 'a (requires comparison) while add has a concrete type int * int -> int, apparently inferred from its first use in the program. Both are declared and used in the same way, so why the difference in generalization?
I understand that in the case of add, the problem can be side-stepped by declaring the function inline, which causes it to get a generic type definition, i.e. 'a * 'b -> 'c (requires member (+)), but that doesn’t explain why this is needed in this case and not the other.
There is an excellent write up on this very issue by @TomasP here: http://tomasp.net/blog/fsharp-generic-numeric.aspx
But why are
<and>(and by extension,=,<=and>=) OK?The F# compiler treats
equalityandcomparisondifferently (see section 5.2.10 Equality and Comparison Constraints in the specs, thanks @Daniel). You get the specialcomparisonconstraint, which is allowed when (simply, see the spec for more detail):There is no such special handling for the
+operator. Why couldn’t there be a constraint such asnumeric?Well isn’t that operator also defined for strings? In some languages for lists and collections? Surely it would be an
addableconstraint and notnumeric. Then many such overloaded operators can be found in a program with different semantic meaning. So F# provides a ‘catch-all’ method with static member constraints and the inline keyword. Onlyequalityandcomparisonare special.