Swift Generics ...检查与相关类型的协议的一致性

时间:2019-04-09 03:24:41

标签: swift generics protocols associated-types

我试图通过将通用类型设置为Int来在Swift中编写一个通用函数,该通用函数可以接受FloatDouble<T: Numeric>等任何数字。所以,

func doSomething<T: Numeric>(with foo: T, and bar: T) -> T {
  // body
}

大部分身体都可以用于任何Numeric类型,但是在此过程中,我需要找到其余部分...这意味着对于FloatingPoint类型与{{ 1}}类型。

IntegerT时,这意味着使用模运算符:

Int

但是,当let remainder = foo % bar TDouble时,模运算符不起作用,我需要使用Float方法:

truncatingRemainder(dividingBy:)

我在努力的地方是找到一种方法来筛选这些。从理论上讲,我应该能够执行以下操作:

let remainder = foo.truncatingRemainder(dividingBy: bar)

这当然会导致此错误,因为var remainder: T if T.self as FloatingPoint { // <- This doesn't work remainder = foo.truncatingRemainder(dividingBy: bar) } else { remainder = foo % bar } 具有关联的类型:

FloatingPoint

我理解此错误...本质上,error: protocol 'FloatingPoint' can only be used as a generic constraint because it has Self or associated type requirements 是通用的,具有尚通用的关联类型供采用的类型定义。

但是,我想知道最好的方法来有条件地运行选择的代码块,这些代码块仅适用于比通用参数(FloatingPoint定义的更狭窄定义的协议。

特别是,有一种方法可以定义一个通用函数来处理所有T类型,然后通过Numeric与内部的FloatingPoint类型进行区分。

2 个答案:

答案 0 :(得分:1)

这里有几个问题。

  1. 如果您想取余数,则数字是错误的协议。

除非我误读了Numeric的文档,否则矩阵类型可以合理地符合Numeric。如果将矩阵传递到函数中,则您将没有真正的方法来计算余数,因为这不是一个很好定义的概念。因此,不应在所有数值类型上都定义您的函数。解决方案是定义一个新协议,该协议描述具有明确定义的余数的类型。不幸的是,正如亚历山大在评论中指出的那样...

  1. Swift不允许您扩展协议以符合其他协议。

此限制有多种技术原因,主要集中在类型将以多种方式符合协议的困难上。

我认为您有两个合理的解决方案。

A。进行doSomething的两个不同的重载,一个重载T: FloatingPoint,另一个重载T: BinaryInteger。如果这些实现之间共享的代码太多,则可以将doSomething重构为多个函数,其中大多数将在所有Numeric上定义。

B。引入一个新协议RemainderArithmetic: Numeric,该协议描述了所需的操作。然后,为要使用的所有特定类型写明确的一致性,例如extension Double: RemainderArithmeticextension UInt: RemainderArithmetic

这两种解决方案都没有特别吸引人,但我认为它们具有一个关键优势:这两种解决方案都清楚说明了您期望的类型的特殊语义。除了BinaryIntegerFloatingPoint之外的其他类型,您还没有真正预期,所以您不应该接受其他类型。 wikipedia page for mod中描述的多种行为证明了余数的语义非常棘手。因此,跨整数和浮点数以相同的方式定义函数可能不是很自然。如果确定这是您要执行的操作,那么这两种解决方案都将使您对期望的类型做出明确的假设。

如果您对这两种解决方案都不满意,并且可以提供有关您到底想做什么的更多详细信息,我们也许可以找到其他东西。

答案 1 :(得分:1)

这听起来不像是泛型的好用例。您会注意到,+之类的运算符在Swift中不是通用的。他们使用超载,您也应该这样做。