我在HackingWithSwift中读到,只有当元素数小于或等于6时,Swift元组才能与==
运算符进行比较.这一限制背后的原因是什么?
我在HackingWithSwift中读到,只有当元素数小于或等于6时,Swift元组才能与==
运算符进行比较.这一限制背后的原因是什么?
Equatable
Swift 的元组不是Equatable
,而且(目前)它们实际上不可能是Equatable
.不可能写出这样的东西:
extension (T1, T2): Equatable { // Invalid
// ...
}
这是因为Swift 的元组是structural types:它们的身份来源于它们的 struct .就像我的(Int, String)
一样.
您可以将其与nominal types进行对比,nominal types的身份完全基于其名称(以及定义它们的模块的名称),其 struct 不相关.enum E1 { case a, b }
不同于enum E2 { case a, b }
,尽管在 struct 上是等效的.
在Swift中,只有名义类型可以符合协议(比如Equatble
),这使得元组无法参与.
==
operators exist尽管如此,标准库还是提供了==
个用于比较元组的运算符.(但请记住,因为仍然不符合Equatable
,所以不能将元组传递给预期为可等式类型的函数,例如func f<T: Equatable>(input: T)
.)
必须 for each 元组算术手动定义一个==
运算符,如:
public func == <A: Equatable, B: Equatable, >(lhs: (A,B ), rhs: (A,B )) -> Bool { ... }
public func == <A: Equatable, B: Equatable, C: Equatable, >(lhs: (A,B,C ), rhs: (A,B,C )) -> Bool { ... }
public func == <A: Equatable, B: Equatable, C: Equatable, D: Equatable, >(lhs: (A,B,C,D ), rhs: (A,B,C,D )) -> Bool { ... }
public func == <A: Equatable, B: Equatable, C: Equatable, D: Equatable, E: Equatable, >(lhs: (A,B,C,D,E ), rhs: (A,B,C,D,E )) -> Bool { ... }
public func == <A: Equatable, B: Equatable, C: Equatable, D: Equatable, E: Equatable, F: Equatable>(lhs: (A,B,C,D,E,F), rhs: (A,B,C,D,E,F)) -> Bool { ... }
当然,手写出来会很乏味.相反,它是使用GYB("生成样板文件")编写的,这是一个轻量级Python模板工具.它允许库作者使用以下工具实现==
:
% for arity in range(2,7):
% typeParams = [chr(ord("A") + i) for i in range(arity)]
% tupleT = "({})".format(",".join(typeParams))
% equatableTypeParams = ", ".join(["{}: Equatable".format(c) for c in typeParams])
// ...
@inlinable // trivial-implementation
public func == <${equatableTypeParams}>(lhs: ${tupleT}, rhs: ${tupleT}) -> Bool {
guard lhs.0 == rhs.0 else { return false }
/*tail*/ return (
${", ".join("lhs.{}".format(i) for i in range(1, arity))}
) == (
${", ".join("rhs.{}".format(i) for i in range(1, arity))}
)
}
然后由GYB扩展到:
@inlinable // trivial-implementation
public func == <A: Equatable, B: Equatable>(lhs: (A,B), rhs: (A,B)) -> Bool {
guard lhs.0 == rhs.0 else { return false }
/*tail*/ return (
lhs.1
) == (
rhs.1
)
}
@inlinable // trivial-implementation
public func == <A: Equatable, B: Equatable, C: Equatable>(lhs: (A,B,C), rhs: (A,B,C)) -> Bool {
guard lhs.0 == rhs.0 else { return false }
/*tail*/ return (
lhs.1, lhs.2
) == (
rhs.1, rhs.2
)
}
@inlinable // trivial-implementation
public func == <A: Equatable, B: Equatable, C: Equatable, D: Equatable>(lhs: (A,B,C,D), rhs: (A,B,C,D)) -> Bool {
guard lhs.0 == rhs.0 else { return false }
/*tail*/ return (
lhs.1, lhs.2, lhs.3
) == (
rhs.1, rhs.2, rhs.3
)
}
@inlinable // trivial-implementation
public func == <A: Equatable, B: Equatable, C: Equatable, D: Equatable, E: Equatable>(lhs: (A,B,C,D,E), rhs: (A,B,C,D,E)) -> Bool {
guard lhs.0 == rhs.0 else { return false }
/*tail*/ return (
lhs.1, lhs.2, lhs.3, lhs.4
) == (
rhs.1, rhs.2, rhs.3, rhs.4
)
}
@inlinable // trivial-implementation
public func == <A: Equatable, B: Equatable, C: Equatable, D: Equatable, E: Equatable, F: Equatable>(lhs: (A,B,C,D,E,F), rhs: (A,B,C,D,E,F)) -> Bool {
guard lhs.0 == rhs.0 else { return false }
/*tail*/ return (
lhs.1, lhs.2, lhs.3, lhs.4, lhs.5
) == (
rhs.1, rhs.2, rhs.3, rhs.4, rhs.5
)
}
尽管他们自动化了这个样板,理论上可以将for arity in range(2,7):
改为for arity in range(2,999):
,但仍然有成本:所有这些实现都必须编译并生成机器代码,最终导致标准库inflating .因此,仍然需要切断电源.图书馆的作者 Select 了6个,尽管我不知道他们是如何确定这个数字的.
有两种方法可以在future 改善这一点:
有一个快速发展的宣传(尚未实施,因此还没有正式提案)来引入Variadic generics,其中明确提到这是一个激励性的例子:
最后,元组在Swift语言中一直占有特殊的地位,但处理任意元组仍然是当今的挑战.特别是,无法扩展元组,因此像Swift标准库这样的客户机必须采用类似的样板文件繁重方法,并在每个算术运算中为比较运算符定义特殊重载.在那里,标准库 Select 人为地将其重载集限制为长度在2到7之间的元组,每增加一个重载都会给类型判断器带来更大的压力.特别值得注意的是:本提案为非名义一致性奠定了基础,但此类一致性的语法超出了范围.
这一拟议的语言功能将允许一个人写:
public func == <T...>(lhs: T..., rhs: T...) where T: Equatable -> Bool {
for (l, r) in zip(lhs, rhs) {
guard l == r else { return false }
}
return true
}
这将是一个通用的==
运算符,可以处理元组或任何算术.
人们还希望支持non-nominal conformances,允许元组等 struct 类型符合协议(如Equatable
).
这会让人想到:
extension<T...> (T...): Equatable where T: Equatable {
public static func == (lhs: Self, rhs: Self) -> Bool {
for (l, r) in zip(lhs, rhs) {
guard l == r else { return false }
}
return true
}
}