为什么自动泛化有时会推断出过于具体的类型?

Why is automatic generalization sometimes inferring overly specific types?

当运算符的一侧具有已知类型而另一侧没有时,某些函数用法无法编译。一个例子是计量单位:

let inline multiplyWithFive x = 5. * x

type [<Measure>] myUnit
let test = multiplyWithFive 3.<myUnit> // Compiler error

5. * 3.<myUnit> 显然是一个有效的表达式,所以这是令人惊讶的,特别是考虑到 inline 函数在其他情况下最大限度地泛化:

let inline multiply a b = a * b
let test = multiply 5. 3.<myUnit> // Valid

但这并不限于度量单位。比方说,我创建了一个支持带浮点数的非对称乘法的类型。它与 multiplyWithFive 函数不兼容,后者任意推断其参数为 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

同样,5. * boxThree 是一个有效的表达式。但是自动概括似乎并没有承认这一点。

我可以 "fix" 第一种情况,带有度量单位感知类型注释,但这无缘无故地限制了基础类型。如果我真的需要一个更通用的功能,我不知道如何阻止编译器进行限制。如果我明确命名一个泛型参数,它只是拒绝保持它的泛型:

// Warning: This construct causes code to be less generic than indicated...
let inline multiplyWithFive (x : 'T) = 5. * x 

我该怎么办?有什么方法可以表明我确实想要更通用的版本吗?

为了回答您的第一个问题,我认为 F# 编译器不会自动将泛型类型概括为包括可能的单元,所以这就是您的第一个示例最终采用 float 的原因。如果您在类型注释中指定一个带单位的浮点数,它会在单位上泛化:

let inline multiplyWithFive (x:float<_>) = 5. * x

type [<Measure>] myUnit
multiplyWithFive 3.         // Works fine without units
multiplyWithFive 3.<myUnit> // Works fine with units

至于使它适用于任何支持乘法运算符的类型——我认为 F# 编译器对乘法运算符进行特殊封装,以便在您真正只想使用 [ 的典型情况下提供合理的默认行为=12=] 或 float<_> 值。如果您使用显式成员约束定义它,则警告非常清楚:

// 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))

您可能可以通过以更奇特的方式使用静态成员约束来解决此问题 - trick that lets you define overloaded operators 使用静态成员。但是,我认为这有点扩展了机制,它可能会对代码的其他部分产生可用性影响。