为什么自动泛化有时会推断过度特定的类型?

Van*_*oiy 4 generics f# inline automatic-generalization

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

let inline multiplyWithFive x = 5. * x

type [<Measure>] myUnit
let test = multiplyWithFive 3.<myUnit> // Compiler error
Run Code Online (Sandbox Code Playgroud)

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

let inline multiply a b = a * b
let test = multiply 5. 3.<myUnit> // Valid
Run Code Online (Sandbox Code Playgroud)

但是,这不仅限于计量单位.比方说,我做了一个支持浮点数不对称乘法的类型.它与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
Run Code Online (Sandbox Code Playgroud)

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

我可以使用度量单位感知类型注释来"修复"第一个案例,但这无条件地限制了基础类型.如果我真的需要一个更通用的功能,我不知道如何阻止编译器制定限制.如果我明确地命名一个泛型参数,它只是拒绝保持它的通用性:

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

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

Tom*_*cek 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
Run Code Online (Sandbox Code Playgroud)

至于使用支持乘法运算符的任何类型都可以使用它 - 我认为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))
Run Code Online (Sandbox Code Playgroud)

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