3

考虑以下代码:

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.

4

3 回答 3

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;}
于 2013-02-23T18:12:46.007 回答
2

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

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

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

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

如果你使用这个定义而不是你原来的定义,它至少应该允许你使用操作符——但是它给出了关于期望一个“Base”实例的相同错误消息(这很奇怪)。

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

于 2013-02-23T14:57:11.603 回答
2

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

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

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

open BaseOps

let aInherited = Inherited(5.0)
cos aInherited // OK
-aInherited    // OK
于 2013-02-23T14:59:07.400 回答