【问题标题】:How can I make unary negation and math functions work on inherited types?如何使一元否定和数学函数适用于继承的类型?
【发布时间】:2013-02-09 02:04:11
【问题描述】:

考虑以下代码:

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中。

【问题讨论】:

    标签: .net f#


    【解决方案1】:

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

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

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

    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;}
    

    【讨论】:

      【解决方案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
      

      【讨论】:

      • 解决方法的好主意,但是像 cos 5.0 这样的东西失败了 'float' 类型与'Base' 类型不兼容。 不完全是什么我也想要。
      • @JeffreySax cos 5.0 不起作用,因为与 C# 不同,没有隐式转换运算符。 F# 编译器根本猜不到必须先将 5.0 转换为 Base,然后再应用 -
      • @bytebuster 我知道这就是它失败的原因。问题是这段代码隐藏了cos 的原始定义,因此导致像cos 5.0 这样的表达式(否则可以正常工作)不再编译。
      • @JeffreySax:您需要在#Basefloat 上交替或在同一行上使用cos?您可以 open 一个模块多次在运算符之间切换。对你的情况来说可能太乏味了……不知道。
      • @Daniel 我没有意识到你可以这样切换模块上下文。尽管如此,#Basefloat 上的操作通常可以写在同一个表达式中。
      【解决方案3】:

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

      type Inherited(x : float) = 
          inherit Base(x)
      
          static member (~-) (a : Inherited) =
              -(a :> Base)
      
          static member Cos (a : Inherited) =
              cos (a :> Base)
      

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

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

      【讨论】:

      • 我不认为这是一个错误,这是 F# 处理运算符的方式,首先查看全局范围,找到具有特定签名的全局定义,强制两种类型相同。因此错误消息。
      • @Gustavo 如果是这种情况,并且这种行为是意料之中的,我希望会有更好的错误消息——关于继承运算符或其他什么的。由于它只是重用了标准的与键入相关的错误消息,因此(在我看来)这是一个被忽略的运算符重载决议的边缘案例。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-12-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多