4

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

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是一个有效的表达式。但自动泛化似乎并未承认这一点。

我可以使用可识别度量单位的类型注释来“修复”第一种情况,但这无缘无故地限制了基础类型。如果我真的需要一个更通用的功能,我不知道如何阻止编译器弥补限制。如果我明确命名一个泛型参数,它只是拒绝保持其泛型:

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

我能做些什么呢?有什么办法可以说我确实想要更通用的版本吗?

4

1 回答 1

5

为了回答您的第一个问题,我认为 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# 编译器对乘法运算符进行了特殊封装,以便在您真正只想使用floatfloat<_>值的典型情况下提供合理的默认行为。如果您使用显式成员约束来定义它,则警告非常清楚:

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

您可能可以通过以更奇特的方式使用静态成员约束来解决此问题 - 有一个技巧可以让您使用静态 memebrs 定义重载运算符。但是,我认为这会稍微扩展机制,并且可能会对代码的其他部分产生可用性影响。

于 2016-12-13T15:39:55.957 回答