【问题标题】:What is the purpose of declaring typealias for generic type parameter为泛型类型参数声明类型别名的目的是什么
【发布时间】:2019-06-25 11:21:08
【问题描述】:

考虑以下代码:

public struct Foo<E: Hashable>: Equatable, Collection 
{
    public typealias Element = E
    private var array: [Element]
...
}

这种编码实践可以在很多地方找到,包括像https://github.com/apple/swift-package-manager/blob/master/Sources/Basic/OrderedSet.swift这样的苹果官方代码库

声明类型别名Element而不是直接使用Element作为泛型类型参数名称的目的是什么:

public struct Foo<Element: Hashable> 
{
    private var array: [Element]
...
}

仅仅是某种编码偏好,还是有其他一些原因,比如实现一些没有这种简单的 typealias 声明就无法实现的功能?

【问题讨论】:

  • 可能这只是一种最小化泛型参数列表的方法,以及Foo&lt;E: Hashable&gt; vs Foo&lt;E&gt; where E: Hashable
  • 代码明显是复制粘贴的。有趣的是它有一个扩展名where Element: Hashable { },因为Element 总是Hashable。当您必须明确声明 typealias 时,也许起源日期为 Swift 版本?
  • @Sulthan 一切皆有可能...我无法再跟踪所有更改...但是如果没有 typealias,您将无法做一件事,那就是访问泛型类型之外的泛型类型参数.

标签: swift generics


【解决方案1】:

声明公共类型别名可以访问封闭泛型类型之外的泛型类型参数。

例如,如果您声明类型别名WidgetFoo = Foo&lt;Widget&gt; 并在其他地方继续使用WidgetFoo,则可以通过WidgetFoo.Element(指Widget)访问其T,而您无法访问泛型类型参数E 本身。这可以实现健壮且重构友好的代码 - 假设您想将 Widget 替换为 BetterWidget,您只需更改一个位置(类型别名声明)而无需更改其他位置,因为 WidgetFoo.Element 将引用 BetterWidget


示例代码(@Airspeed Velocity 提供)

struct S<T> { typealias U = T }

typealias TA = S<Int>

let x: TA.T // 'T' is not a member type of 'TA' (aka 'S<Int>')
let y: TA.U

【讨论】:

  • 我不明白你的目的是什么。你能给出一个演示的源示例吗?我试图重现您所描述的内容,并且没有区别(因为 typealias 是 alias;它没有定义新类型)
  • 这不是 Dalija 问题的正确答案。再往下看我更完整的回复
  • @JoannaCarter 其实这就是答案(当然可能还有其他方面这里没有介绍)twitter.com/AirspeedSwift/status/1143993690794254336
【解决方案2】:

这里有一个简单的例子,说明为什么您(有时)需要在实现协议的类型中声明类型别名。

Collection 协议是“通用的”,因为它包含“占位符”类型,在 Swift 语法中称为关联类型。集合包括两个必须在实现类型中解析的关联类型:元素和索引。

在这个堆栈示例中,我们可以从声明类型别名开始,这将告诉编译器我们将使用哪些具体类型来满足协议中的关联类型(如果要匹配,itemT 需要从 Hashable 派生协议中对 Element 关联类型的要求):

class Stack<itemT : Hashable> : Collection
{
  typealias Element = itemT

  typealias Index = Int

  lazy var items: [itemT] = .init()

  func push(item: itemT)
  {
    items.append(item)
  }

  func pop() -> itemT?
  {
    guard let result = items.last,
          let lastIndex = items.lastIndex(of: result) else
    {
      return nil
    }

    items.remove(at: lastIndex)

    return result
  }
}

现在这意味着编译器知道 Collection 的 Element 类型将是我们在使用堆栈时用作 itemT 类型的任何内容。 Collection 的 Index 类型将是 Int,因为这是我们用来保存堆栈项的数组的索引类型

不幸的是,这还不足以满足 Collection 协议的要求,因为还有一些“抽象”方法也必须实现。因此,我们将这些方法添加到我们的堆栈类中。然后,这将告诉编译器所需方法的签名,并允许建议正确的 Fixits,如果我们应用它们,则会为我们提供以下代码:

class Stack<itemT : Hashable> : Collection
{
  typealias Element = itemT

  typealias Index = Int

  lazy var items: [itemT] = .init()

  func push(item: itemT)
  {
    items.append(item)
  }

  func pop() -> itemT?
  {
    guard let result = items.last,
          let lastIndex = items.lastIndex(of: result) else
    {
      return nil
    }

    items.remove(at: lastIndex)

    return result
  }

  var startIndex: Int
  {
    return items.startIndex
  }

  var endIndex: Int
  {
    return items.endIndex
  }

  func index(after i: Int) -> Int
  {
    return items.index(after: i)
  }

  subscript(position: Int) -> itemT
  {
    return items[position]
  }
}

终于,类编译了!作为编译器现在建议其“抽象”方法的正确类型实现的副作用,它不再需要我们告诉它协议中的哪些关联类型需要在我们的实现类型中链接到。因此,我们现在可以从代码中删除类型别名:

class Stack<itemT : Hashable> : Collection
{
  lazy var items: [itemT] = .init()

  func push(item: itemT)
  {
    items.append(item)
  }

  func pop() -> itemT?
  {
    guard let result = items.last,
          let lastIndex = items.lastIndex(of: result) else
    {
      return nil
    }

    items.remove(at: lastIndex)

    return result
  }

  var startIndex: Int
  {
    return items.startIndex
  }

  var endIndex: Int
  {
    return items.endIndex
  }

  func index(after i: Int) -> Int
  {
    return items.index(after: i)
  }

  subscript(position: Int) -> itemT
  {
    return items[position]
  }
}

我们是否需要为关联类型声明类型别名通常取决于给定协议中关联类型的复杂性。

【讨论】:

  • 您在这里所说的基本上是使用 typealiases 使编译器能够识别出符合 Collection 协议的确切缺失并提供自动修复。这确实是真的,因为如果没有索引类型别名,它会显示“无法推断协议'集合'的关联类型'索引'”。但是,这里的 Element 似乎并不是关键部分。我无法完全确认,因为修复被破坏了,因为他们试图在 Swift.Collection 而不是我的代码中添加修复。
【解决方案3】:

typealiasassociatedtype 表示可以隐藏类型参数的值,成为实现细节而不是类型的公共接口。这在定义扩展、属性、方法、引用超类/协议等时消除了很多噪音。

Russ Bishop 用出色的例子很好地解释了这一点 here

【讨论】:

  • 这不是答案,因为 E(或 Element)仍然是 Foo 类型声明的一部分 - Foo&lt;E: Hashable&gt;
  • 你能给出一个代码示例,这个隐藏实际上可以工作吗?由于Collection 接口和typealias 都是公共的,EElement 的分离如何隐藏调用代码的任何内容?你能改变什么直接使用: Element 不允许?
  • Russ Bishop 文章不再有效,因为它在协议中使用旧的 typealias 语法而不是当前的 associatedtype。我将在回复 Dalija 的帖子中进一步解释
【解决方案4】:

在您引用的代码中,Element 是来自 Collection 协议的通用参数。 typealias 将协议参数类型绑定到实现该协议的通用具体类型。

在此处查看标题关联类型下的文档: https://developer.apple.com/documentation/swift/collection#relationships

【讨论】:

  • 如果泛型参数直接命名为Element 会不会同样如此(我的实验表明是)?
【解决方案5】:

我们似乎在这里谈论不同的目的。

你引用这段代码:

public struct Foo<E: Hashable>: Equatable, Collection 
{
  public typealias Element = E

  private var array: [Element]

  …
}

... 其中泛型struct Foo&lt;E : Hashable&gt; 有一个占位符E(这是实现Hashable 所必需的,以便用于E 的任何类型都与Collection 协议中声明的Element 参数兼容);并且struct Foo&lt;E&gt; 也是实现集合(通用)协议所必需的。

typealias Element = E 行用于将泛型结构的E 参数绑定到(泛型)集合协议中的Element 关联类型;仅此而已。这就是我向您提供使用类型别名的示例以帮助编译器为您提供完全实现 Collection 协议所需的缺失“抽象”方法的原因。

另一方面,您的代码:

public struct Foo<Element: Hashable> 
{
  private var array: [Element]
  …
}

... 与实现协议无关;它只是一个带有名为 Element 的占位符参数的通用结构,它是实现 Hashable 所必需的。

这一次,该结构只包含一个数组,其中包含您使用Foo&lt;Element&gt; 时使用的任何元素。不需要类型别名,因为没有任何东西可以绑定 Element 占位符;它的用途与第一个示例中的 E 相同,但没有结构实现 Collection 的位。

我必须承认对@Airspeed Velocity 的代码感到困惑:

struct S<T> { typealias U = T }

typealias TA = S<Int>

let x: TA.T // 'T' is not a member type of 'TA' (aka 'S<Int>')
let y: TA.U

… 旨在实现。为什么要为已知且可用的类型T 指定类型别名U?在很多很多年的泛型编程中,我还没有遇到过这种情况,除非它是另一种编写我可能在 C# 泛型中使用过的东西的方式。

让我将 Swift 自己的 Collection 协议替换为一个专为你设计的 Dalija:

protocol DalijaCollection
{
  associatedtype ItemType : Hashable // placeholder - does the same as Element from Collection

  func append(item: ItemType)

  func remove(item: ItemType)

  var count: Int { get }

  subscript(index: Int) -> ItemType { get }
}


class Stack<itemT : Hashable> : DalijaCollection
{
  typealias ItemType = itemT // not needed once all the members of DalijaConnection have been implemented

  lazy var items: [itemT] = .init()

  func push(item: itemT)
  {
    items.append(item)
  }

  func pop() -> itemT?
  {
    guard let result = items.last,
          let lastIndex = items.lastIndex(of: result) else
    {
      return nil
    }

    items.remove(at: lastIndex)

    return result
  }

  func append(item: itemT)
  {
    items.append(item)
  }

  func remove(item: itemT)
  {
    guard let index = items.index(of: item) else
    {
      fatalError("item not found")
    }

    items.remove(at: index)
  }

  var count: Int
  {
    return items.count
  }

  subscript(index: Int) -> itemT
  {
    return items[index]
  }
}

使用 DalijaCollection 版本,您可以这样做:

  while stack.count > 0
  {
    print(stack.pop())
  }

  // or

  for i in 0..<stack.count
  {
    print(stack[i])
  }

... Swift 的 Collection 协议还允许您像这样使用 for…in 构造:

{
  for item in stack
  {
    print(item)
  }
}

……还有更多。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-12-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多