为什么自动泛化有时会推断出过于具体的类型?
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 使用静态成员。但是,我认为这有点扩展了机制,它可能会对代码的其他部分产生可用性影响。
当运算符的一侧具有已知类型而另一侧没有时,某些函数用法无法编译。一个例子是计量单位:
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 使用静态成员。但是,我认为这有点扩展了机制,它可能会对代码的其他部分产生可用性影响。