将基于“MyProtocol”的泛型函数的参数更改为使用存在的“any MyProtocol”或“some MyProtocol”是否会受到惩罚?

Mar*_*eIV 3 generics existential-type swift

通常在讨论诸如 之类的事情时some,它们指的是返回类型。这个问题具体是关于在参数列表中使用anyor 的some

例如,在 的 Swift 文档中String,您有这个初始化程序...

init<T>(_ value: T, radix: Int = 10, uppercase: Bool = false)
where T : BinaryInteger
Run Code Online (Sandbox Code Playgroud)

在 Swift 5.6 中,他们引入了any关键字,让我们更轻松地处理存在类型。通过这一更改,我知道理论上您可以像这样重写上面的内容......

init(_ value: any BinaryInteger, radix: Int = 10, uppercase: Bool = false)
Run Code Online (Sandbox Code Playgroud)

当然还有这个基于some关键字的版本,也可以...

init(_ value: some BinaryInteger, radix: Int = 10, uppercase: Bool = false)
Run Code Online (Sandbox Code Playgroud)

我的问题是……哪一个最有意义?使用存在类型而不是像这样的泛型有缺点吗?版本怎么样some?我最初认为是的,通用版本是最好的,因为编译器可以在编译时确定传递给它的内容,但话又说回来,存在any甚至some版本也是如此,因为如果不传递它,它就不会编译aBinaryInteger并且我不太确定如何编写测试来检查这一点。

Ita*_*ber 8

传递协议类型的值时P给 Swift 中的方法时,当前可以使用 4 种可能的拼写:

\n
    \n
  1. func f<T: P>(_ value: T)(“通用的”)
  2. \n
  3. func f(_ value: some P)(“不透明参数”)
  4. \n
  5. func f(_ value: P)(“‘裸露’存在主义”)
  6. \n
  7. func f(_ value: any P)(“\'显式\'存在主义”)
  8. \n
\n

在这些拼写中,(1) 和(2) 是同义词,目前,(3) 和(4) 是同义词。使用(1)和(2)之间没有区别,(3)和(4)之间目前*没有区别;但在 (1)/(2) 和 (3)/(4) 之间,

\n
    \n
  1. f<T: P>(_: T)是采用保证符合协议的具体类型参数的传统方法TP。这种获取参数的方式:\n
      \n
    • 使您可以在编译时和运行时访问具体类型T,以便您可以执行操作T,以便您可以对其自身执行操作
    • \n
    • 与类型一样没有开销T在编译时已知,并且编译器知道值的大小和布局,并且可以适当地设置堆栈/寄存器;它可以将提供给它的任何参数直接传递给该方法
    • \n
    • 仅当参数的类型静态已知时(在编译时)才能调用;但因此,可以使用协议类型通过Self- 或associatedtype要求的协议类型进行调用
    • \n
    \n
  2. \n
  3. 在SE-0341(不透明参数声明)中引入,采用的方法的版本some Protocol正是通用版本我将避免重复提案的内容,但简介部分阐明了对此语法的渴望,以此来简化拼写通用参数的复杂性
  4. \n
  5. f(_: P)是采用保证符合协议的存在类型参数的传统方法P。这种获取参数的方式:\n
      \n
    • 在编译时不提供对参数具体底层类型的访问;尽管这可以在运行时动态访问type(of:)
    • \n
    • 将参数传递给方法以及访问方法内部的值时都有运行时开销:因为方法的参数类型可能无法静态获知(而编译器仍然需要知道如何设置堆栈并注册以调用该方法),参数必须装在“存在框”中,该框具有以下接口:P并且可以动态地将方法传递给底层的具体值。这既有在运行时分配额外“盒子”以将实际值保存在大小一致且布局一致的容器内的成本,也有间接成本,因为盒子上的方法调用必须动态分派到底层类型
    • \n
    • 无论参数的类型是否静态已知,都可以调用;因此,不能Self使用具有- 或associatedtype要求的协议类型进行调用
    • \n
    \n
  6. \n
  7. 在SE-0335 (Existential Any)中引入,any协议类型前面的关键字是一个标记,有助于指示该类型被用作存在类型。现在它与使用协议的裸名称(即)完全相同,但已经有一些关于最终使用协议的裸名称来表示的讨论any P == Psome P
  8. \n
\n

因此,要解决您的具体示例:

\n
init(_ value: some BinaryInteger, radix: Int = 10, uppercase: Bool = false)\n
Run Code Online (Sandbox Code Playgroud)\n

与原来完全相同

\n
init<T>(_ value: T, radix: Int = 10, uppercase: Bool = false)\nwhere T : BinaryInteger\n
Run Code Online (Sandbox Code Playgroud)\n

尽管

\n
init(_ value: any BinaryInteger, radix: Int = 10, uppercase: Bool = false)\n
Run Code Online (Sandbox Code Playgroud)\n

不是。而且,由于BinaryIntegerassociatedtype要求,您也不能使用该any版本,因为存在类型不会提供对底层关联类型的访问。(如果你尝试,你就会得到经典error: protocol \'BinaryInteger\' can only be used as a generic constraint because it has Self or associated type requirements

\n
\n

一般来说缺乏开销并且具有更大的灵活性;但是,它们需要知道参数的静态类型,但这并不总是可能的。

\n

存在主义更容易接受输入,但功能明显受到限制,并且需要付出代价。

\n

<T: P>在和some P、或P和之间any P\xe2\x80\x94 之间,选择目前是主观的,但是:

\n
    \n
  1. 作为 SE-0335 的一部分,目前计划 Swift 6 将要求使用any关键字来表示存在协议的使用;与使用它相对应的是some,因此,如果您想开始使您的代码面向未来并保持一致,那么开始迁移到any和可能是一个好主意some
  2. \n
  3. 在不透明参数语法和泛型语法之间,选择取决于您,但不透明参数目前无法涵盖泛型可以覆盖的所有用例,尤其是在具有更复杂的约束的情况下。无论是在各处坚持使用泛型还是更喜欢不透明参数并仅在必要时使用泛型,都是您需要做出的代码风格选择
  4. \n
\n

  • 我当然认为现在应该使用风格上的“any”而不是纯粹的协议名称,因为它比纯粹的协议名称更能说明其含义。裸露的协议名称一直在误导和困惑程序员。需要“any”是件好事。 (2认同)