Swift 协议的二元运算符 returns 接收者类型(符合协议)

Swift binary operator for protocols that returns the receiver type (which conforms to the Protocol)

对于 Friday Fun,我想以可互换的格式对 Angles 进行建模。我不确定我是否以最 Swift 惯用的方式完成了它,但我正在学习。所以我有一个 Angle 协议,然后是 3 种不同的结构类型(弧度、度数和旋转),它们都符合 Angle 协议。我希望能够 add/subtract 它们,但诀窍是,我希望 lhs 参数指示 return 类型。例如:

Degrees(180) + Rotations(0.25) --> Degrees(270)

Rotations(0.25) + Radians(M_PI) -> Rotations(0.75)

我希望我能做类似

的事情
func + (lhs:Angle, rhs:Angle) -> Angle {
    return lhs.dynamicType(rawRadians: lhs.rawRadians + rhs.rawRadians)
}

Angle 协议需要 var rawRadians:CGFloat { get } 以及 init(rawRadians:CGFloat)

我可以使用类似 Smalltalk 的双重分派方法来做到这一点,但我认为大多数 Swift 更合适的方法(尤其是需要较少代码的方法,双重分派需要大量样板代码) .

你只需要一个通用的添加:

func +<A: Angle>(lhs: A, rhs: Angle) -> A {
    return A(rawRadians: lhs.rawRadians + rhs.rawRadians)
}

这样加法将 return lhs 上的任何类型。

一般来说,如果您使用 dynamicType,您可能正在战斗 Swift。 Swift 更多地依赖泛型和协议(即编译时的静态类型信息)而不是动态调度(即运行时的动态类型信息)。

在这段代码中,正如您所说,A 是 "some type, that conforms to Angle, to be determined at compile time." 的占位符 所以在您的第一个示例中:

Degrees(180) + Rotations(0.25) --> Degrees(270)

这实际上调用了一个专门的函数+<Degrees>。还有这个:

Rotations(0.25) + Radians(M_PI) -> Rotations(0.75)

调用一个(逻辑上)不同的函数 +<Rotations>。编译器可能会选择将这些函数优化为单个函数,但逻辑上它们是独立的函数,在编译时创建。它基本上是手写 addDegrees(Degrees, Angle)addRotations(Rotations, Angle) 的快捷方式。

现在,关于一个函数的问题,这个函数需要两个角度和 returns……好吧,什么?在这种情况下,如果您想 return 一个 Angle,那很简单,并且与您的原始签名完全匹配:

func +(lhs: Angle, rhs: Angle) -> Angle {
    return Radians(rawRadians: lhs.rawRadians + rhs.rawRadians)
}

"But..." 你是说,"that returns Radians." 不,它不是。它 returns Angle。你可以对它做任何你想做的事"angle-like"。实施细节本应是不透明的。如果您关心底层数据结构是 Radians,那么您几乎肯定做错了什么。

好吧,有一种情况,了解这一点可能会有用,那就是如果你根据你的方式打印出来。因此,如果用户为您提供学位信息作为开始,那么您希望以度为单位打印所有内容(使用您未提及的 description 方法)。也许在您想要的特定 case.If 中值得这样做,您的原始代码非常接近:

func +(lhs: Angle, rhs: Angle) -> Angle {
    return lhs.dynamicType.init(rawRadians: lhs.rawRadians + rhs.rawRadians)
}

但重要的是要了解这与您对 "the lhs argument to dictate the return type." 的要求不符 这会导致 lhs 参数指示 return 实现 。 return 类型 总是Angle。如果要更改 return type,则需要使用泛型。