定义需要特定类型序列的 Swift 协议
Define a Swift protocol which requires a specific type of sequence
假设我们正在讨论 Int 类型的元素(但问题仍然适用于任何类型)
我有一些功能需要循环一个 Int 序列。但我不关心这个序列在幕后是否被实现为数组、集合或任何其他奇特的结构,唯一的要求是我们可以循环遍历它们。
Swift 标准库将协议 SequenceType 定义为 "A type that can be iterated with a for...in loop"。所以我的直觉是定义一个这样的协议:
protocol HasSequenceOfInts {
var seq : SequenceType<Int> { get }
}
但这行不通。 SequenceType 不是可以专门化的通用类型,它是一种协议。任何特定的 SequenceType 确实 具有特定类型的元素,但它仅可作为关联类型使用:SequenceType.Generator.Element
所以问题是:
我们如何定义一个需要特定序列类型的协议?
这是我尝试过的其他一些方法以及它们不正确的原因:
失败 1
protocol HasSequenceOfInts {
var seq : SequenceType { get }
}
Protocol 'SequenceType' can only be used as a generic constraint
because it has Self or associated type requirements
失败 2
protocol HasSequenceOfInts {
var seq : AnySequence<Int> { get }
}
class ArrayOfInts : HasSequenceOfInts {
var seq : [Int] = [0,1,2]
}
我认为这个可行,但当我尝试使用数组的具体实现时,我们得到
Type 'ArrayOfInts' does not conform to protocol 'HasSequenceOfInts'
这是因为 Array 不是 AnySequence(令我惊讶的是......我的期望是 AnySequence 会匹配任何 Int 序列)
失败 3
protocol HasSequenceOfInts {
typealias S : SequenceType
var seq : S { get }
}
编译,但序列 seq 的元素没有义务具有 Int 类型
失败 4
protocol HasSequenceOfInts {
var seq : SequenceType where S.Generator.Element == Int
}
那里不能使用 where 子句
所以现在我完全没有想法了。我可以很容易地让我的协议需要一个 Int 数组,但是我无缘无故地限制了实现,这感觉很不 swift.
更新成功
请参阅@rob-napier 的回答,它很好地解释了事情。我的失败 2 非常接近。使用 AnySequence 可以工作,但在您的 class 中,您需要确保从您正在使用的任何类型的序列转换为 AnySequence。例如:
protocol HasSequenceOfInts {
var seq : AnySequence<Int> { get }
}
class ArrayOfInts : HasSequenceOfInts {
var _seq : [Int] = [0,1,2]
var seq : AnySequence<Int> {
get {
return AnySequence(self._seq)
}
}
}
我认为您需要放弃它只能是 Int 的要求,并使用泛型解决它:
protocol HasSequence {
typealias S : SequenceType
var seq : S { get }
}
struct A : HasSequence {
var seq = [1, 2, 3]
}
struct B : HasSequence {
var seq : Set<String> = ["a", "b", "c"]
}
func printSum<T : HasSequence where T.S.Generator.Element == Int>(t : T) {
print(t.seq.reduce(0, combine: +))
}
printSum(A())
printSum(B()) // Error: B.S.Generator.Element != Int
在Swift的当前状态下,您无法完全按照自己的意愿行事,但也许在未来。
这是应 Daniel Howard 要求的非常具体的例子
1) 符合SequenceType协议的类型几乎可以是任何序列,即使Array或Set都符合SequenceType协议,它们的大部分功能都来自于CollectionType(符合SequenceType)的继承
Daniel,在你的 Playground 中试试这个简单的例子
import Foundation
public struct RandomIntGenerator: GeneratorType, SequenceType {
public func next() -> Int? {
return random()
}
public func nextValue() -> Int {
return next()!
}
public func generate() -> RandomIntGenerator {
return self
}
}
let rs = RandomIntGenerator()
for r in rs {
print(r)
}
如您所见,它符合 SequenceType 协议并产生无限的 Int 数字流。在你尝试实施之前,你必须回答自己几个问题
- 我可以重用标准 Swift 库中 'for free' 的某些功能吗?
- 我是否在尝试模仿 Swift 不支持的某些功能? Swift 不是 C++,Swift 不是 ObjectiveC ... 我们在 Swift 之前使用的很多结构在 Swift.
中没有等价物
- 以我们可以理解您的要求的方式定义您的问题
您在找这样的东西吗?
protocol P {
typealias Type: SequenceType
var value: Type { get set }
}
extension P {
func foo() {
for v in value {
dump(v)
}
}
}
struct S<T: CollectionType>: P {
typealias Type = T
var value: Type
}
var s = S(value: [Int]())
s.value.append(1)
s.value.append(2)
s.foo()
/*
- 1
- 2
*/
let set: Set<String> = ["alfa", "beta", "gama"]
let s2 = S(value: set)
s2.foo()
/*
- beta
- alfa
- gama
*/
// !!!! WARNING !!!
// this is NOT possible
s = s2
// error: cannot assign value of type 'S<Set<String>>' to type 'S<[Int]>' (aka 'S<Array<Int>>')
这个问题有两个方面:
接受任意整数序列
返回或存储任意整数序列
第一种情况,答案是使用泛型。例如:
func iterateOverInts<SeqInt: SequenceType where SeqInt.Generator.Element == Int>(xs: SeqInt) {
for x in xs {
print(x)
}
}
第二种情况,你需要一个打字机。类型擦除器是一个包装器,它隐藏了一些底层实现的实际类型并且只呈现接口。 Swift 在 stdlib 中有几个,大多以单词 Any
作为前缀。在这种情况下,您需要 AnySequence
.
func doubles(xs: [Int]) -> AnySequence<Int> {
return AnySequence( xs.lazy.map { [=11=] * 2 } )
}
有关 AnySequence
和一般类型橡皮擦的更多信息,请参阅 A Little Respect for AnySequence。
如果你需要它的协议形式(通常你不需要;你只需要像 iterateOverInts
那样使用泛型),类型橡皮擦也是那里的工具:
protocol HasSequenceOfInts {
var seq : AnySequence<Int> { get }
}
但是seq
必须returnAnySequence<Int>
。不能 return [Int]
.
你可以再深入一层,但有时它制造的麻烦多于它解决的问题。您可以定义:
protocol HasSequenceOfInts {
typealias SeqInt : IntegerType
var seq: SeqInt { get }
}
但现在 HasSequenceOfInts
有一个 typealias
具有所有暗示的限制。 SeqInt
可以是任何类型的 IntegerType
(不仅仅是 Int
),所以看起来就像一个受约束的 SequenceType
,并且通常需要它自己的类型橡皮擦。因此,有时这种技术很有用,但在您的特定情况下,它只会让您基本上回到起点。您不能在此处将 SeqInt
限制为 Int
。它必须是一个协议(当然你可以发明一个协议并使 Int
成为唯一符合的类型,但这并没有太大变化)。
顺便说一句,关于类型橡皮擦,您可能会看到它们非常机械。它们只是一个转发到其他东西的盒子。这表明将来编译器将能够为我们自动生成这些类型擦除器。随着时间的推移,编译器已经为我们修复了其他装箱问题。例如,您过去必须创建一个 Box
class 来保存具有通用关联值的枚举。现在,这是使用 indirect
半自动完成的。我们可以想象添加一个类似的机制来在编译器需要时自动创建 AnySequence
。所以我不认为这是一个深"Swift's design doesn't allow it."我认为它只是"the Swift compiler doesn't handle it yet."
(在 Swift 4,which introduces the associatedtype
constraints needed for this 中测试和工作)
声明事物将遵守的原始协议:
protocol HasSequenceOfInts {
associatedType IntSequence : Sequence where IntSequence.Element == Int
var seq : IntSequence { get }
}
现在,您可以写
class ArrayOfInts : HasSequenceOfInts {
var seq : [Int] = [0,1,2]
}
如你所愿。
但是,如果您尝试创建类型为 [HasSequenceOfInts]
的数组,或将其分配给一个变量(或基本上用它做任何事情),您将收到一条错误消息
Protocol 'HasSequenceOfInts' can only be used as a generic constraint because it has Self or associated type requirements
现在是有趣的部分。
我们将创建另一个协议HasSequenceOfInts_
(随意选择一个更具描述性的名称)它不会有关联的类型要求,和会自动符合 HasSequenceOfInts
:
protocol HasSequenceOfInts: HasSequenceOfInts_ {
associatedType IntSequence : Sequence where IntSequence.Element == Int
var seq : IntSequence { get }
}
protocol HasSequenceOfInts_ {
var seq : AnySequence<Int> { get }
}
extension HasSequenceOfInts_ where Self : HasSequenceOfInts {
var seq_ : AnySequence<Int> {
return AnySequence(seq)
}
}
请注意,您永远不需要明确符合 HasSequenceOfInts_
,因为 HasSequenceOfInts
已经符合它,并且您可以从扩展中免费获得完整的实现。
现在,如果您需要创建一个数组或将符合此协议的实例分配给变量,请使用 HasSequenceOfInts_
作为类型而不是 HasSequenceOfInts
,并访问 seq_
属性 (注: 因为函数重载是允许的,如果你做一个函数seq()
而不是实例变量,你可以给它起同样的名字它会起作用):
let a: HasSequenceOfInts_ = ArrayOfInts()
a.seq_
这需要比接受的答案多一点设置,但这意味着您不必记住将 return 值包装在 AnySequence(...)
中 every 输入你实现协议的地方。
假设我们正在讨论 Int 类型的元素(但问题仍然适用于任何类型)
我有一些功能需要循环一个 Int 序列。但我不关心这个序列在幕后是否被实现为数组、集合或任何其他奇特的结构,唯一的要求是我们可以循环遍历它们。
Swift 标准库将协议 SequenceType 定义为 "A type that can be iterated with a for...in loop"。所以我的直觉是定义一个这样的协议:
protocol HasSequenceOfInts {
var seq : SequenceType<Int> { get }
}
但这行不通。 SequenceType 不是可以专门化的通用类型,它是一种协议。任何特定的 SequenceType 确实 具有特定类型的元素,但它仅可作为关联类型使用:SequenceType.Generator.Element
所以问题是:
我们如何定义一个需要特定序列类型的协议?
这是我尝试过的其他一些方法以及它们不正确的原因:
失败 1
protocol HasSequenceOfInts {
var seq : SequenceType { get }
}
Protocol 'SequenceType' can only be used as a generic constraint because it has Self or associated type requirements
失败 2
protocol HasSequenceOfInts {
var seq : AnySequence<Int> { get }
}
class ArrayOfInts : HasSequenceOfInts {
var seq : [Int] = [0,1,2]
}
我认为这个可行,但当我尝试使用数组的具体实现时,我们得到
Type 'ArrayOfInts' does not conform to protocol 'HasSequenceOfInts'
这是因为 Array 不是 AnySequence(令我惊讶的是......我的期望是 AnySequence 会匹配任何 Int 序列)
失败 3
protocol HasSequenceOfInts {
typealias S : SequenceType
var seq : S { get }
}
编译,但序列 seq 的元素没有义务具有 Int 类型
失败 4
protocol HasSequenceOfInts {
var seq : SequenceType where S.Generator.Element == Int
}
那里不能使用 where 子句
所以现在我完全没有想法了。我可以很容易地让我的协议需要一个 Int 数组,但是我无缘无故地限制了实现,这感觉很不 swift.
更新成功
请参阅@rob-napier 的回答,它很好地解释了事情。我的失败 2 非常接近。使用 AnySequence 可以工作,但在您的 class 中,您需要确保从您正在使用的任何类型的序列转换为 AnySequence。例如:
protocol HasSequenceOfInts {
var seq : AnySequence<Int> { get }
}
class ArrayOfInts : HasSequenceOfInts {
var _seq : [Int] = [0,1,2]
var seq : AnySequence<Int> {
get {
return AnySequence(self._seq)
}
}
}
我认为您需要放弃它只能是 Int 的要求,并使用泛型解决它:
protocol HasSequence {
typealias S : SequenceType
var seq : S { get }
}
struct A : HasSequence {
var seq = [1, 2, 3]
}
struct B : HasSequence {
var seq : Set<String> = ["a", "b", "c"]
}
func printSum<T : HasSequence where T.S.Generator.Element == Int>(t : T) {
print(t.seq.reduce(0, combine: +))
}
printSum(A())
printSum(B()) // Error: B.S.Generator.Element != Int
在Swift的当前状态下,您无法完全按照自己的意愿行事,但也许在未来。
这是应 Daniel Howard 要求的非常具体的例子
1) 符合SequenceType协议的类型几乎可以是任何序列,即使Array或Set都符合SequenceType协议,它们的大部分功能都来自于CollectionType(符合SequenceType)的继承
Daniel,在你的 Playground 中试试这个简单的例子
import Foundation
public struct RandomIntGenerator: GeneratorType, SequenceType {
public func next() -> Int? {
return random()
}
public func nextValue() -> Int {
return next()!
}
public func generate() -> RandomIntGenerator {
return self
}
}
let rs = RandomIntGenerator()
for r in rs {
print(r)
}
如您所见,它符合 SequenceType 协议并产生无限的 Int 数字流。在你尝试实施之前,你必须回答自己几个问题
- 我可以重用标准 Swift 库中 'for free' 的某些功能吗?
- 我是否在尝试模仿 Swift 不支持的某些功能? Swift 不是 C++,Swift 不是 ObjectiveC ... 我们在 Swift 之前使用的很多结构在 Swift. 中没有等价物
- 以我们可以理解您的要求的方式定义您的问题
您在找这样的东西吗?
protocol P {
typealias Type: SequenceType
var value: Type { get set }
}
extension P {
func foo() {
for v in value {
dump(v)
}
}
}
struct S<T: CollectionType>: P {
typealias Type = T
var value: Type
}
var s = S(value: [Int]())
s.value.append(1)
s.value.append(2)
s.foo()
/*
- 1
- 2
*/
let set: Set<String> = ["alfa", "beta", "gama"]
let s2 = S(value: set)
s2.foo()
/*
- beta
- alfa
- gama
*/
// !!!! WARNING !!!
// this is NOT possible
s = s2
// error: cannot assign value of type 'S<Set<String>>' to type 'S<[Int]>' (aka 'S<Array<Int>>')
这个问题有两个方面:
接受任意整数序列
返回或存储任意整数序列
第一种情况,答案是使用泛型。例如:
func iterateOverInts<SeqInt: SequenceType where SeqInt.Generator.Element == Int>(xs: SeqInt) {
for x in xs {
print(x)
}
}
第二种情况,你需要一个打字机。类型擦除器是一个包装器,它隐藏了一些底层实现的实际类型并且只呈现接口。 Swift 在 stdlib 中有几个,大多以单词 Any
作为前缀。在这种情况下,您需要 AnySequence
.
func doubles(xs: [Int]) -> AnySequence<Int> {
return AnySequence( xs.lazy.map { [=11=] * 2 } )
}
有关 AnySequence
和一般类型橡皮擦的更多信息,请参阅 A Little Respect for AnySequence。
如果你需要它的协议形式(通常你不需要;你只需要像 iterateOverInts
那样使用泛型),类型橡皮擦也是那里的工具:
protocol HasSequenceOfInts {
var seq : AnySequence<Int> { get }
}
但是seq
必须returnAnySequence<Int>
。不能 return [Int]
.
你可以再深入一层,但有时它制造的麻烦多于它解决的问题。您可以定义:
protocol HasSequenceOfInts {
typealias SeqInt : IntegerType
var seq: SeqInt { get }
}
但现在 HasSequenceOfInts
有一个 typealias
具有所有暗示的限制。 SeqInt
可以是任何类型的 IntegerType
(不仅仅是 Int
),所以看起来就像一个受约束的 SequenceType
,并且通常需要它自己的类型橡皮擦。因此,有时这种技术很有用,但在您的特定情况下,它只会让您基本上回到起点。您不能在此处将 SeqInt
限制为 Int
。它必须是一个协议(当然你可以发明一个协议并使 Int
成为唯一符合的类型,但这并没有太大变化)。
顺便说一句,关于类型橡皮擦,您可能会看到它们非常机械。它们只是一个转发到其他东西的盒子。这表明将来编译器将能够为我们自动生成这些类型擦除器。随着时间的推移,编译器已经为我们修复了其他装箱问题。例如,您过去必须创建一个 Box
class 来保存具有通用关联值的枚举。现在,这是使用 indirect
半自动完成的。我们可以想象添加一个类似的机制来在编译器需要时自动创建 AnySequence
。所以我不认为这是一个深"Swift's design doesn't allow it."我认为它只是"the Swift compiler doesn't handle it yet."
(在 Swift 4,which introduces the associatedtype
constraints needed for this 中测试和工作)
声明事物将遵守的原始协议:
protocol HasSequenceOfInts {
associatedType IntSequence : Sequence where IntSequence.Element == Int
var seq : IntSequence { get }
}
现在,您可以写
class ArrayOfInts : HasSequenceOfInts {
var seq : [Int] = [0,1,2]
}
如你所愿。
但是,如果您尝试创建类型为 [HasSequenceOfInts]
的数组,或将其分配给一个变量(或基本上用它做任何事情),您将收到一条错误消息
Protocol 'HasSequenceOfInts' can only be used as a generic constraint because it has Self or associated type requirements
现在是有趣的部分。
我们将创建另一个协议HasSequenceOfInts_
(随意选择一个更具描述性的名称)它不会有关联的类型要求,和会自动符合 HasSequenceOfInts
:
protocol HasSequenceOfInts: HasSequenceOfInts_ {
associatedType IntSequence : Sequence where IntSequence.Element == Int
var seq : IntSequence { get }
}
protocol HasSequenceOfInts_ {
var seq : AnySequence<Int> { get }
}
extension HasSequenceOfInts_ where Self : HasSequenceOfInts {
var seq_ : AnySequence<Int> {
return AnySequence(seq)
}
}
请注意,您永远不需要明确符合 HasSequenceOfInts_
,因为 HasSequenceOfInts
已经符合它,并且您可以从扩展中免费获得完整的实现。
现在,如果您需要创建一个数组或将符合此协议的实例分配给变量,请使用 HasSequenceOfInts_
作为类型而不是 HasSequenceOfInts
,并访问 seq_
属性 (注: 因为函数重载是允许的,如果你做一个函数seq()
而不是实例变量,你可以给它起同样的名字它会起作用):
let a: HasSequenceOfInts_ = ArrayOfInts()
a.seq_
这需要比接受的答案多一点设置,但这意味着您不必记住将 return 值包装在 AnySequence(...)
中 every 输入你实现协议的地方。