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

时间:2016-12-13 15:37:20

标签: generics f# inline automatic-generalization

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

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 是一个有效的表达式。但是自动概括似乎没有承认这一点。

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

String.split "from" "target string: blah... from ..."
    |> String.join "to"

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

1 个答案:

答案 0 :(得分: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#编译器是乘法运算符的特殊外壳,因此它在典型的情况下提供合理的默认行为,你真的只想使用{{1 }或float值。如果使用显式成员约束来定义它,则警告非常清楚:

float<_>

你可以通过更加花哨的方式使用静态成员约束来解决这个问题 - 有一个trick that lets you define overloaded operators使用静态memebrs。但是,我认为这会稍微扩展机制,并且可能会对代码的其他部分产生可用性影响。