为什么Swift元组只有元素个数小于等于6时才能比较?
Why Swift Tuples can be compared only when the number of elements is less than or equal to 6?
我在HackingWithSwift中读到Swift元组只有在元素个数小于或等于6时才可以与==
运算符进行比较,这背后的原因是什么限制?
背景:元组不是 Equatable
Swift 的元组不是 Equatable
,而且它们实际上不可能(目前)。写这样的东西是不可能的:
extension (T1, T2): Equatable { // Invalid
// ...
}
这是因为Swift的元组是结构类型:它们的身份是从它们的结构派生出来的。你的 (Int, String)
和我的 (Int, String)
一样。
您可以将其与 名义类型 进行对比,后者的身份完全基于它们的名称(好吧,以及定义它们的模块的名称),并且其结构无关紧要. enum E1 { case a, b }
不同于 enum E2 { case a, b }
,尽管它们在结构上是等价的。
在Swift中,只有名义类型可以符合协议(如Equatble
),这使得元组无法参与。
...但存在 ==
个运算符
尽管如此,==
用于比较元组的运算符由标准库提供。 (但请记住,由于仍然不符合 Equatable
,您不能将元组传递给需要 Equatable 类型的函数,例如 func f<T: Equatable>(input: T)
。)
必须为每个元组元数手动定义一个 ==
运算符,例如:
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 { ... }
当然,这对于手工 write-out 来说真的很乏味。相反,它是使用 GYB(“生成样板”)编写的,这是一种 light-weight 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):
,但仍然存在成本:所有这些实现都必须编译并生成最终导致膨胀的机器代码标准库。因此,仍然需要中断。库作者选择了 6,但我不知道他们是如何特别确定这个数字的。
未来
未来可能会通过两种方式改进:
有一个Swift Evolution pitch(尚未实施,所以还没有官方提案)来介绍Variadic generics,它明确提到这是激励示例之一:
Finally, tuples have always held a special place in the Swift language, but working with arbitrary tuples remains a challenge today. In particular, there is no way to extend tuples, and so clients like the Swift Standard Library must take a similarly boilerplate-heavy approach and define special overloads at each arity for the comparison operators. There, the Standard Library chooses to artificially limit its overload set to tuples of length between 2 and 7, with each additional overload placing ever more strain on the type checker. Of particular note: This proposal lays the ground work for non-nominal conformances, but syntax for such conformances are out of scope.
这一提议的语言功能将允许编写:
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
}
这将是一个可以处理元组或任何元数的 general-purpose ==
运算符。
也有兴趣潜在支持 non-nominal conformances,允许元组等结构类型符合协议(如 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
}
}
我在HackingWithSwift中读到Swift元组只有在元素个数小于或等于6时才可以与==
运算符进行比较,这背后的原因是什么限制?
背景:元组不是 Equatable
Swift 的元组不是 Equatable
,而且它们实际上不可能(目前)。写这样的东西是不可能的:
extension (T1, T2): Equatable { // Invalid
// ...
}
这是因为Swift的元组是结构类型:它们的身份是从它们的结构派生出来的。你的 (Int, String)
和我的 (Int, String)
一样。
您可以将其与 名义类型 进行对比,后者的身份完全基于它们的名称(好吧,以及定义它们的模块的名称),并且其结构无关紧要. enum E1 { case a, b }
不同于 enum E2 { case a, b }
,尽管它们在结构上是等价的。
在Swift中,只有名义类型可以符合协议(如Equatble
),这使得元组无法参与。
...但存在 ==
个运算符
尽管如此,==
用于比较元组的运算符由标准库提供。 (但请记住,由于仍然不符合 Equatable
,您不能将元组传递给需要 Equatable 类型的函数,例如 func f<T: Equatable>(input: T)
。)
必须为每个元组元数手动定义一个 ==
运算符,例如:
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 { ... }
当然,这对于手工 write-out 来说真的很乏味。相反,它是使用 GYB(“生成样板”)编写的,这是一种 light-weight 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):
,但仍然存在成本:所有这些实现都必须编译并生成最终导致膨胀的机器代码标准库。因此,仍然需要中断。库作者选择了 6,但我不知道他们是如何特别确定这个数字的。
未来
未来可能会通过两种方式改进:
有一个Swift Evolution pitch(尚未实施,所以还没有官方提案)来介绍Variadic generics,它明确提到这是激励示例之一:
Finally, tuples have always held a special place in the Swift language, but working with arbitrary tuples remains a challenge today. In particular, there is no way to extend tuples, and so clients like the Swift Standard Library must take a similarly boilerplate-heavy approach and define special overloads at each arity for the comparison operators. There, the Standard Library chooses to artificially limit its overload set to tuples of length between 2 and 7, with each additional overload placing ever more strain on the type checker. Of particular note: This proposal lays the ground work for non-nominal conformances, but syntax for such conformances are out of scope.
这一提议的语言功能将允许编写:
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 }
这将是一个可以处理元组或任何元数的 general-purpose
==
运算符。也有兴趣潜在支持 non-nominal conformances,允许元组等结构类型符合协议(如
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 } }