斯威夫特的数字协议

我在新加坡iOS Conf的Jesse Squires演讲中,他讨论了Swift的数字类型和协议。 这让我想起了SE-0104(面向协议的整数),现在该实现是在Swift 4中进行的,认为现在是仔细研究的好时机。

让我们从每个人喜欢的整数类型Int的协议层次结构开始:

与标准库中的大多数类型一样, Int符合很多协议! 从头到尾, Numeric是基本协议之一。 所有不同大小的整数类型( UIntInt8等)以及浮点类型( FloatDouble等)都符合Numeric

数值,从0x00开始

您可以在Integers.swift.gyb中遵循标准库源代码,尽管照常,所有相关代码都将在下面内联。

这是开始的协议声明:

 public protocol Numeric : Equatable, ExpressibleByIntegerLiteral { 

这里有另外两个符合性:

  • Equatable增加了对==运算符的支持。
  • ExpressibleByIntegerLiteral允许使用let a: Int = 42这样的代码let a: Int = 42其中42是上述的“整数文字”。

这意味着所有数字类型都可以使用整数文字进行初始化,但不能使用浮点文字进行初始化。 这段代码可以:

 let someFloat: Float = 42 // ✅ 

但这不行:

 let someInt: Int = 4.2 // 🙅 

您将在特定的浮点类型中遇到ExpressibleByFloatLiteral协议,但不会在一般的Numeric类型级别上遇到。

初始化

该协议具有一个必需的初始化程序:

 init?(exactly source: T) 

这是一个失败的初始化程序,参数标签中的“完全”应该告诉您原因:如果尝试使用超出其可容纳范围的值初始化实例,则初始化程序将返回nil

 let ok = Int8(exactly: 10) // 10 
let tooBig = Int8(exactly: 300) // nil

还要注意对源类型的约束:它必须符合BinaryIntegerBinaryInteger符合Numeric因此对于像Numeric这样的“父”协议来说,需要一种符合像BinaryInteger这样的“子”协议的BinaryInteger来构造自身似乎有点怪异。 这里发生了什么?

表示与使用

关于BinaryIntegerNumeric这两种协议的BinaryInteger快速BinaryInteger

根据标题文档, BinaryInteger就是它在BinaryInteger上说的:“一个具有二进制表示形式的整数类型”。 对于许多程序员而言,这是非常熟悉的,因为我们一直使用以位为单位的整数。

另一方面, Numeric与表示无关,与用法无关。 该协议“为标量值的算术提供了合适的基础”。

好,然后回到算术。 🤓

大小

Magnitude是数字的绝对值,因此42.magnitude-42.magnitude都是42

 associatedtype Magnitude : Comparable, Numeric 
var magnitude: Magnitude { get }

magnitude计算的属性必须是Numeric并且也是Comparable 。 这意味着一个数字本身不必具有可比性,但其大小却可以。

当您查看诸如Int之类的类型的具体实现时,您会看到它使用magnitude进行一些算术,计算数字之间的距离等。

算术

我们已经达到了我们最喜欢的算术运算:加法!

 static func + (_ lhs: Self, _ rhs: Self) -> Self 
static func +=(_ lhs: inout Self, rhs: Self)

第一个加法函数采用两个值并产生和。 第二个是变异版本,其中“左侧”自变量( a += 10中的a += 10 )被变异。

在您自己的数字类型中,至少应提供变异运算符的实现。

如果您已经编码了符合Equatable类型,那么您会记得需要为==提供实现,然后免费获得!=

同样,通常的模式是用+=定义+ 。 例如,具体标准库类型UInt16+的实现使用+=

 // Inside the UInt16 implementation 
public static func +(_ lhs: UInt16, _ rhs: UInt16) -> UInt16 {
var lhs = lhs
lhs += rhs
return lhs
}

最后, Numeric协议还要求运算符进行减法和乘法运算:

 static func - (_ lhs: Self, _ rhs: Self) -> Self 
static func -=(_ lhs: inout Self, rhs: Self)
 static func * (_ lhs: Self, _ rhs: Self) -> Self 
static func *=(_ lhs: inout Self, rhs: Self)

在应为自己的符合类型提供变异版本的地方,也适用相同的准则。

没说的事情

具体数字类型的作用远远超出了此协议中此处定义的范围。 正如您从本文顶部的图中所看到的,有很多协议组合在一起,使某些事情像Swift整数一样复杂。

就是说,我们只介绍了Numeric ,只看了我们习惯于使用数值类型的所有功能的一部分。 缺少什么大的东西?

  • 除法 -我们已经看到了加法,减法和乘法运算,但是缺少的算术族成员是什么? 除法在BinaryIntegerFloatingPoint协议中分别定义。
    协议中的功能定义相同,但规格略有不同:整数除法会丢弃其余部分,而浮点除法则遵循IEEE-754规则。
  • 浮点运算 -您看到了符合Numeric类型如何也是ExpressibleByIntegerLiteral但不能用float文字表达。 整数可以直接转换为浮点数,但是从浮点数转换为整数时,您需要考虑舍入。 此舍入如何工作? 对于Numeric这超出了范围。
  • 可比 —同样,这是在BinaryIntegerFloatingPoint协议级别上发现的,除了您之前看到的magnitude属性。 这两个协议都符合Strideable ,这反过来又意味着Comparable

结束括号

Numeric协议提供了数值类型的基础:

  • 用整数值初始化
  • 平等的
  • 简单算术
  • 确定其潜在价值的量级概念

除了上一节中列出的缺失内容外,该协议对存储也没有意见。 请记住, Numeric是关于数字的用途 ,而其他协议(例如BinaryInteger则是关于表示形式以及如何存储值的。

准备建立自己的自定义vigesimal数值类型了吗? 😉

}