如何使一元否定和数学函数适用于继承类型?

时间:2013-02-23 14:07:29

标签: .net f#

请考虑以下代码:

type Base(x : float) =
    member this.x = x
    static member (~-) (a : #Base) = Base(-a.x)
    static member Cos (a : #Base) = Base(cos a.x)

type Inherited(x : float) = 
    inherit Base(x)

let aBase = Base(5.0)
let aInherited = Inherited(5.0)

-aBase                // OK, returns Base(-5.0)
-(aInherited :> Base) // OK, returns Base(-5.0)
-aInherited           // not OK

最后一行产生错误:

error FS0001: This expression was expected to have type
    Inherited    
but here has type
    Base    

cos aInherited相同:它会出现同样的错误,但-(aInherited :> Base)cos (aInherited :> Base)可以正常工作。

错误消息表明这些函数希望返回类型-cos与参数类型相同。这似乎是一个过于苛刻的要求。

  • 对于从定义运算符的基类型继承的类,除非重新定义每个运算符,否则这是不可能的。
  • 如果这些类位于您无法控制的外部库中,则您的选项会更加有限。

有解决方法吗?在F#源代码中,cos函数在prim-types.fs中定义。

3 个答案:

答案 0 :(得分:4)

我认为没有干净的方法可以做到这一点。

问题是这些运算符的原始全局定义的签名返回与输入相同的类型,因此如果不重新定义全局定义,您将无法添加不尊重此签名的静态成员。

如果您使用受限制较少的签名创建新的全局定义,则必须处理所有情况,否则我可以想象的唯一可以重用全局定义的方法是通过中间类型来对抗类型推断:

type Base(x : float) =
    member this.x = x

type Inherited(x : float) = 
    inherit Base(x)

type UnaryNeg = UnaryNeg with
    static member inline ($) (UnaryNeg, a       ) = fun (_         ) -> -a
    static member        ($) (UnaryNeg, a: #Base) = fun (_:UnaryNeg) -> Base(-a.x)
let inline (~-) a = (UnaryNeg $ a) UnaryNeg

type Cos = Cos with
    static member inline ($) (Cos, a       ) = fun (_    ) -> cos a
    static member        ($) (Cos, a: #Base) = fun (_:Cos) -> Base(cos a.x)    
let inline cos a = (Cos $ a) Cos

这适用于所有情况和任何派生类型的Base:

> cos 0.5  ;;
val it : float = 0.8775825619
> cos (Base 0.5)  ;;
val it : Base = FSI_0002+Base {x = 0.8775825619;}
> cos (Inherited 0.5)  ;;
val it : Base = FSI_0002+Base {x = 0.8775825619;}
> type Inherited2(x : float) =     inherit Base(x) ;;
> cos (Inherited2 0.5)  ;;
val it : Base = FSI_0002+Base {x = 0.8775825619;}

答案 1 :(得分:2)

它变得更有趣。我认为您可以使用快速,hacky解决方案,只需重新定义继承类型的运算符并使用它们来调用基类运算符,但即使在继承类型上定义运算符之后,仍然会收到错误消息最后一个案例(这很奇怪)。

type Inherited(x : float) = 
    inherit Base(x)

    static member (~-) (a : Inherited) =
        -(a :> Base)

    static member Cos (a : Inherited) =
        cos (a :> Base)

如果您使用此定义而不是原始定义,应该至少允许您使用运算符 - 但它会提供有关期望“Base”实例的相同错误消息(这很奇怪。)

我的猜测是你发现了编译器错误,或者至少是语言规范中的边缘情况。您应该通过fsbugs将其发送至microsoft.com,以便他们在下一版本中解决此问题。

答案 2 :(得分:2)

您可以将运算符放在模块中:

module BaseOps =
  let (~-) (a: #Base) = Base(-a.x)
  let cos (a: #Base) = Base(cos a.x)

open隐藏内置运算符的模块。然后,您不再受限于预期的签名(它还会避开任何潜在的错误)。这与Core lib用于checked operators的技术相同。

open BaseOps

let aInherited = Inherited(5.0)
cos aInherited // OK
-aInherited    // OK