我在HackingWithSwift中读到,只有当元素数小于或等于6时,Swift元组才能与==运算符进行比较.这一限制背后的原因是什么?

推荐答案

Background: Tuples aren't 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),这使得元组无法参与.

...but == 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 改善这一点:

  1. 有一个快速发展的宣传(尚未实施,因此还没有正式提案)来引入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
    }
    

    这将是一个通用的==运算符,可以处理元组或任何算术.

  2. 人们还希望支持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
        }
    }
    

Swift相关问答推荐

{Singleton类}类型的值没有成员$showegView'

多个提取来计算核心数据中的记录

如何使用RxSwift根据数据数绘制UICollectionView单元格

UITableView未显示类数组数据

Variadic泛型与Swift中的值和类型参数包

如何在visionOS中进行购买?&# 39;购买(选项:)在visionOS中不可用

Swift通过设置变量B自动设置变量A的值

计算 CoreData 中所有唯一对象的数量?

为什么自定义串行队列的目标是全局队列时,Swift中不会并发执行?

当变量首次用于其自己的初始化时,如何清除变量...在初始化之前使用错误?

在 Swift 的异步上下文中,如何指示我想要函数的同步版本?

如何在一个视图控制器中创建具有相同行为的多个集合视图?

如何打印出此 struct 中的数据?

Swift并发:为什么不在其他后台线程上执行任务

Swift 有没有办法在不使用 switch 语句的情况下获取关联值?

Vapor, fluent 使用 PostgreSQL 保存/创建复杂模型

如何在 Swift 中重写 setter

swift - 我可以从我的自定义初始化方法中调用 struct 默认成员初始化吗?

Switch 语句中的字符串:String不符合协议IntervalType

滑动侧边栏菜单 IOS 8 Swift