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

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


我该怎么办?有没有办法说我想要更通用的版本?

最佳答案

为了回答您的第一个问题,我认为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#编译器对乘法运算符使用特殊的大小写,以便在您真的只想使用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))


您可能可以通过更花哨的方式使用静态成员约束来解决此问题-使用静态memebres的trick that lets you define overloaded operators。但是,我认为这会使机制有所扩展,并且可能会对代码的其他部分带来可用性后果。

07-24 09:46
查看更多