【问题标题】:Overriding / operator for Optionals using generics results in endless loop使用泛型覆盖 Optionals 的 / 运算符会导致无限循环
【发布时间】:2017-01-19 09:56:43
【问题描述】:

让我们看看下面的代码sn-p:

func / <T>(lhs: T?,rhs: T?) throws -> T? {
    switch (lhs,rhs) {
        case let (l?,r?):
            return try l/r
        default:
            return nil
    }
}

let x : Double? = 2
let y : Double? = 2

let z = try! x/y

我创建了一个需要两个可选参数的通用函数。如果我运行此代码,它会导致无限循环,因为 try l/r 使用 func / &lt;T&gt;(lhs: T?,rhs: T?) 来划分值。谁能解释为什么将两个非可选的 double 值相除会导致对我编写的方法的函数调用,而不是 Double 的默认 / 运算符定义?

如果我通过一个需要该类的静态 / 运算符的扩展来扩展 Double,那么一切都会像魅力一样工作:

protocol Dividable {
    static func /(lhs: Self, rhs: Self) -> Self
}

extension Double: Dividable {}

func / <T:Dividable>(lhs: T?,rhs: T?) throws -> T? {
    switch (lhs,rhs) {
        case let (l?,r?):
            return  l/r
        default:
            return nil
    }
}

let x : Double? = 2
let y : Double? = 2

let z = try! x/y

【问题讨论】:

  • 由于您的 return 语句中的try,它可能会导致递归,因为/ 的内置运算符不会抛出。此外,看起来您的操作员也不会抛出,因此将其标记为 throws 并没有真正的区别,因为它实际上不会抛出。
  • @xoudiniI 可能只是指出上面赞成的评论中推测的原因不正确:您可以使用try 标记调用非抛出函数(如果您这样做,将提示警告),但这与调用的重载解决方案无关。 l/r 未能找到 OP 打算使用的重载的原因在下面的答案中进行了介绍。然而,上面评论的第二部分是相关的(为什么扔?)。
  • 我是这么想的:将两个 Double 相除可能会导致异常(除以 0),所以我需要用 try 标记 l/r。所以该函数的调用者知道它可能会失败..
  • 但正如约瑟夫所说:分母为0时除法会崩溃,不会抛出错误。所以我基本上不确定。
  • @dfri 事实上,我评论的第一部分只是猜测。感谢您的详尽回答——您学到的越多。

标签: swift


【解决方案1】:

例如二进制算术Double 不是使用具体的Double 类型实现的,而是作为符合FloatingPoint 的类型的默认泛型实现:

在您的自定义 / 函数块内,编译器不知道类型持有者 T 符合 FloatingPointl/r 的重载解析将解析为方法本身(因为 @987654331 @ 实现虽然更具体,但在您的自定义实现中,更通用的非约束类型 T 无法访问)。

您可以通过将FloatingPoint 作为类型约束添加到您自己的自定义方法来解决此问题:

func /<T: FloatingPoint>(lhs: T?, rhs: T?) throws -> T? {
    switch (lhs, rhs) {
        case let (l?, r?):
            return try l/r
        default:
            return nil
    }
}

同样,整数类型的二进制算术被实现为默认泛型实现,受限于符合内部协议 _IntegerArithmetic 的类型,公共协议 IntegerArithmetic 也符合。

您可以使用后一种公共协议为整数类型实现自定义运算符函数的重载。

func /<T: IntegerArithmetic>(lhs: T?, rhs: T?) throws -> T? {
    switch (lhs, rhs) {
        case let (l?, r?):
            return try l/r
        default:
            return nil
    }
}

最后,您可能需要考虑为什么要抛出此函数。还请注意,在处理您想要操作的两个可选值时,有一些方法可以简化您的实现,仅在两者都不同于nil 的情况下。例如:

func /<T: FloatingPoint>(lhs: T?, rhs: T?) -> T? {
    return lhs.flatMap { l in rhs.map{ l / $0 } }
}

func /<T: IntegerArithmetic>(lhs: T?, rhs: T?) -> T? {
    return lhs.flatMap { l in rhs.map{ l / $0 } }
}

当然,如果您更喜欢语义而不是简洁,请将您的 switch 语句包装在单个 if 语句中

func /<T: FloatingPoint>(lhs: T?, rhs: T?) -> T? {
    if case let (l?, r?) = (lhs, rhs) {
        return l/r
    }
    return nil
}

func /<T: IntegerArithmetic>(lhs: T?, rhs: T?) -> T? {
    if case let (l?, r?) = (lhs, rhs) {
        return l/r
    }
    return nil
}

【讨论】:

    【解决方案2】:

    您的函数签名不会让编译器知道lhsrhs 的类型,除了它们是同一类型。例如,您可以这样调用您的方法:

    let str1 = "Left string"
    let str2 = "Right string"
    let result = try? str1 / str2
    

    这将导致无限循环,因为编译器知道的唯一方法称为/,它接受两个相同类型的参数(在本例中为String)是您声明的那个; return try l/r 会一遍又一遍地调用你的 func / &lt;T&gt;(lhs: T?,rhs: T?) throws -&gt; T? 方法。

    正如您在问题中提到的,您需要一个参数必须符合的协议。不幸的是there is no existing Number or Dividable protocol that would fit your needs,所以你必须自己做。

    请注意,当分母为 0 时,除法会崩溃并且不会抛出错误,因此您应该能够从函数中删除 throws 关键字,使其成为:

    func / &lt;T:Dividable&gt;(lhs: T?, rhs: T?) -&gt; T?

    编辑以进一步澄清

    如果您考虑一下编译器当时知道的内容,我认为这更有意义。一旦进入函数,编译器就知道lhsrhsT 类型并且是可选的。它不知道T什么,也不知道它的任何属性或功能,只知道它们都是T 类型。一旦你解开这些值,你仍然只知道它们都是T 类型并且是非可选的。即使知道T(在这种情况下)是Double,它也可能是String(根据我上面的例子)。这将要求编译器遍历每个可能的类和结构以找到支持您的方法签名的东西(在本例中为 func / (lhs: Double, rhs: Double) -&gt; Double),它根本无法做到(在合理的时间内),并且会导致不可预测的代码.想象一下,如果您添加了这个全局方法,然后每次在现有的东西(例如Float(10) / Float(5))上使用/,您的方法都会被调用,这会很快变得非常混乱和混乱。

    【讨论】:

    • 好的,throw 部分对我来说很有意义,但是当我从函数签名中删除“throw”时,没有将 T 约束到某些协议,它也会由于某种原因导致无限循环。对我来说,调用“return = l/r”,其中 l 和 r 是普通的 Doubles(无可选)也将导致调用我编写的函数而不是 public func /(lhs: Double, rhs: Double) -> Double 来自 Swift 实现,因为我的函数需要两个可选项 T?而不仅仅是 T。
    • 我添加了更多的解释。希望这会有所帮助:)
    猜你喜欢
    • 2018-11-08
    • 2017-01-03
    • 1970-01-01
    • 2012-05-30
    • 1970-01-01
    • 2015-07-03
    • 1970-01-01
    • 2011-08-23
    • 1970-01-01
    相关资源
    最近更新 更多