Reputation: 6223
Some function usages don't compile when one side of an operator has a known type and the other doesn't. One example is with units of measure:
let inline multiplyWithFive x = 5. * x
type [<Measure>] myUnit
let test = multiplyWithFive 3.<myUnit> // Compiler error
5. * 3.<myUnit>
is clearly a valid expression, so this is surprising, especially considering that inline
functions generalize maximally in other cases:
let inline multiply a b = a * b
let test = multiply 5. 3.<myUnit> // Valid
This isn't limited to units of measure though. Say, I make a type that supports asymmetric multiplication with a float. It's incompatible with the multiplyWithFive
function, which arbitrarily infers its parameter as float
:
type BoxFloat =
{ Value : float }
static member inline (*) (lhs : float, rhs : BoxFloat) =
{ Value = lhs * rhs.Value }
let boxThree = { Value = 3. }
let test2 = multiplyWithFive boxThree // Compiler error
Again, 5. * boxThree
is a valid expression. But the automatic generalization doesn't seem to acknowledge that.
I can "fix" the first case with unit-of-measure-aware type annotations, but this restricts the underlying type for no apparent reason. If I actually need a more general function, I don't know how to stop the compiler from making up restrictions. If I explicitly name a generic parameter, it just refuses to keep it generic:
// Warning: This construct causes code to be less generic than indicated...
let inline multiplyWithFive (x : 'T) = 5. * x
What can I do about this? Is there some way to say that I do want the more generic version?
Upvotes: 4
Views: 94
Reputation: 243051
To answer your first question, I think the F# compiler does not automatically generalize the generic type to include possible units, so that's why your first example ends up taking float
. It gets generalized over unit if you specify a float taking a unit in a type annotation:
let inline multiplyWithFive (x:float<_>) = 5. * x
type [<Measure>] myUnit
multiplyWithFive 3. // Works fine without units
multiplyWithFive 3.<myUnit> // Works fine with units
As for making this work with any type that supports the multiplication operator - I think the F# compiler is special casing the multiplication operator so that it gives reasonable default behaviour in the typical case where you really just want to work with float
or float<_>
values. If you define this using explicit member constraint, the warning is quite clear about that:
// warning FS0077: Member constraints with the name 'op_Multiply'
// are given special status by the F# compiler
let inline amultiplyWithFive (x:^T) : ^R =
(^T : (static member (*) : float * ^T -> ^R) (5.0, x))
// no warning since we're requiring operator **
let inline multiplyWithFive (x:^T) : ^R =
(^T : (static member ( ** ) : float * ^T -> ^R) (5.0, x))
You can probably workaround this by using static member constraints in a more fancy way - there is a trick that lets you define overloaded operators using static memebrs. However, I think that's stretching the mechanism a bit and it will likely have usability consequences in other parts of your code.
Upvotes: 5