首页 > 解决方案 > Swift 中的“存在类型”是什么意思?

问题描述

我正在阅读Swift Evolution 提案 244(不透明的结果类型),但不明白以下内容是什么意思:

... 存在型 ...

可以通过使用存在类型Shape 而不是泛型参数来组合这些转换,但这样做会意味着比预期更多的动态性和运行时开销。

标签: swifttype-theory

解决方案


进化提案本身给出了一个存在类型的例子:

protocol Shape {
  func draw(to: Surface)
}

用作存在类型的示例protocol Shape如下所示

func collides(with: Shape) -> Bool

而不是使用通用参数Other

func collides<Other: Shape>(with: Other) -> Bool

需要注意的是,该Shape协议本身并不是一个存在类型,仅在上面的“协议作为类型”上下文中使用它会从中“创建”一个存在类型。请参阅Swift Core 团队成员的这篇文章

此外,协议目前作为存在类型的拼写具有双重职责,但这种关系一直是混淆的常见来源。

另外,引用Swift Generics Evolution文章(我建议阅读整篇文章,其中更详细地解释了这一点):

区分协议类型和存在类型的最好方法是查看上下文。问问自己:当我看到对像 Shape 这样的协议名称的引用时,它是出现在类型级别还是值级别?回顾一些早期的例子,我们看到:

func addShape<T: Shape>() -> T
// Here, Shape appears at the type level, and so is referencing the protocol type

var shape: Shape = Rectangle()
// Here, Shape appears at the value level, and so creates an existential type

更深的潜水

为什么叫“存在”?我从来没有看到过明确的确认,但我认为该功能受到具有更高级类型系统的语言的启发,例如考虑Haskell 的存在类型

class Buffer -- declaration of type class `Buffer` follows here

data Worker x y = forall b. Buffer b => Worker {
  buffer :: b, 
  input :: x, 
  output :: y
}

这大致相当于这个 Swift 片段(如果我们假设 Swift 的协议或多或少代表 Haskell 的类型类):

protocol Buffer {}

struct Worker<X, Y> {
  let buffer: Buffer
  let input: X
  let output: Y
}

请注意,Haskell 示例在这里使用了forall 量词。您可以将其理解为“对于符合Buffer类型类(Swift 中的“协议”)的所有Worker类型,只要它们的类型参数XY类型参数相同,类型的值就会具有完全相同的类型”。因此,给定

extension String: Buffer {}
extension Data: Buffer {}

Worker(buffer: "", input: 5, output: "five")并且Worker(buffer: Data(), input: 5, output: "five")将具有完全相同的类型。

这是一个强大的功能,它允许诸如异构集合之类的事情,并且可以在更多需要“擦除”值的原始类型并将其“隐藏”在存在类型下的地方使用。像所有强大的功能一样,它可能会被滥用,并且会使代码的类型安全性降低,因此应谨慎使用。

如果您想更深入地了解,请查看具有关联类型 (PAT) 的协议,由于各种原因,目前不能将其用作存在。也有一些广义存在主义提案或多或少地定期提出,但截至 Swift 5.3 还没有具体的提案。事实上,由 OP 链接的原始Opaque Result Types提案可以解决使用 PAT 引起的一些问题,并显着缓解 Swift 中缺乏泛化存在的问题。


推荐阅读