声明泛型类型参数的类型别名的目的是什么

时间:2019-06-25 11:21:08

标签: swift generics

考虑以下代码:

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这样的官方Apple代码库

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

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

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

5 个答案:

答案 0 :(得分:7)

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

例如,如果您声明了类型别名import flask import pickle import pandas as pd with open('model/model.pkl', 'rb') as f: model = pickle.load(f) app = flask.Flask(__name__, template_folder='templates') @app.route('/', methods=['GET', 'POST']) def main(): if flask.request.method == 'GET': return(flask.render_template('main.html')) if flask.request.method == 'POST': week = flask.request.form['week'] grad = flask.request.form['grad'] preis = flask.request.form['preis'] input_variables = pd.DataFrame([[week, grad, preis]], columns=['week', 'grad', 'preis'], dtype=float) prediction = model.predict(input_variables)[0] return flask.render_template('main.html', original_input={'week':week, 'grad':grad, 'preis':preis}, result=prediction, ) if __name__ == '__main__': app.debug = True app.run(host = '0.0.0.0', port = 5000) ,并在其他地方继续使用WidgetFoo = Foo<Widget>,则可以通过WidgetFoo(指的是{{ 1}}),而您不能访问通用类型参数T本身。这将启用健壮且可重构的友好代码-假设您想将WidgetFoo.Element替换为Widget,则只需更改一个位置(类型别名声明),而无需更改其他位置,因为E会引用Widget


示例代码(由@Airspeed Velocity提供)

BetterWidget

答案 1 :(得分:3)

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

Collection协议是“泛型”的,因为它包含“占位符”类型,在Swift语法中称为关联类型。集合包含两个必须在实现类型中解析的关联类型:Element和Index。

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

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,因为这是我们用来保存堆栈项的数组的index类型。

不幸的是,这还不足以满足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]
  }
}

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

答案 2 :(得分:2)

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

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

答案 3 :(得分:2)

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

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

答案 4 :(得分:1)

我们在这里似乎是出于交叉目的。

您引用以下代码:

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

  private var array: [Element]

  …
}

…,其中通用struct Foo<E : Hashable>具有占位符E(实现Hashable所必需,以便用于E的任何类型都将与{{1 }}在Collection协议中声明的参数);并且Element也是实施Collection(通用)协议所必需的。

这里有struct Foo<E>行,用于将通用结构的typealias Element = E参数绑定到(通用)集合协议的E关联类型;仅此而已。这就是我为您提供使用类型别名来帮助编译器为您提供完整实现Collection协议所需的缺少的“抽象”方法的示例的原因。

另一方面,您的代码:

Element

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

这一次,该结构仅包含一个数组,该数组包含使用public struct Foo<Element: Hashable> { private var array: [Element] … } 时使用的任何Element。不需要类型别名,因为没有任何绑定Element占位符的内容。它的作用与第一个示例中的E相同,但没有该结构实现Collection的地方。

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

Foo<Element>

…旨在实现。为什么要使用已知的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 类型的U类型别名?在许多年的泛型编程中,除非我用其他方式编写可能在C#泛型中使用的其他方法,否则我还没有遇到过这种情况。

让我用一个适合您的Dalija替换Swift自己的Collection协议:

T

使用DalijaCollection版本,您可以执行以下操作:

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]
  }
}

…Swift的Collection协议还允许您使用 while stack.count > 0 { print(stack.pop()) } // or for i in 0..<stack.count { print(stack[i]) } 结构,如下所示:

for…in

…以及整个负载更多。