定义一个需要特定类型序列的Swift协议

Dan*_*ard 12 generics protocols swift

假设我们正在谈论Int类型的元素(但问题仍然适用于任何类型)

我有一些功能需要循环一系列Ints.但我不在乎幕后这个序列是作为一个数组,或一个Set或任何其他奇特的结构实现,唯一的要求是我们可以循环它们.

Swift标准库将协议SequenceType定义为"可以使用for ... in循环迭代的类型".所以我的直觉是定义这样的协议:

protocol HasSequenceOfInts {
    var seq : SequenceType<Int> { get }
}
Run Code Online (Sandbox Code Playgroud)

但这不起作用.SequenceType不是可以专用的泛型类型,它是一种协议.任何特定的SequenceType 具有特定类型的元素,但它仅作为关联类型提供:SequenceType.Generator.Element

所以问题是:

我们如何定义需要特定类型序列的协议?

这是我尝试过的其他一些事情,以及为什么他们不对:

失败1

protocol HasSequenceOfInts {
    var seq : SequenceType { get }
}
Run Code Online (Sandbox Code Playgroud)

协议'SequenceType'只能用作通用约束,因为它具有Self或相关类型要求

失败2

protocol HasSequenceOfInts {
    var seq : AnySequence<Int> { get }
}
class ArrayOfInts : HasSequenceOfInts {
    var seq : [Int] = [0,1,2]
}
Run Code Online (Sandbox Code Playgroud)

我认为这个可行,但是当我尝试使用数组的具体实现时,我们得到了

类型'ArrayOfInts'不符合协议'HasSequenceOfInts'

这是因为Array不是AnySequence(令我惊讶的是......我的期望是AnySequence会匹配任何Ints序列)

失败3

protocol HasSequenceOfInts {
    typealias S : SequenceType
    var seq : S { get }
}
Run Code Online (Sandbox Code Playgroud)

编译,但序列seq的元素没有义务类型为Int

失败4

protocol HasSequenceOfInts {
    var seq : SequenceType where S.Generator.Element == Int
}
Run Code Online (Sandbox Code Playgroud)

不能在那里使用where子句

所以现在我完全没有想法.我可以很容易地让我的协议需要一个Int of Array,但是后来我没有充分的理由限制实现,这感觉非常迅速.

更新成功

请参阅@ rob-napier的回答,它解释得非常好.我的失败2非常接近.使用AnySequence可以工作,但是在你的符合要求的类中,你需要确保从你正在使用的任何序列转换为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)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

Rob*_*ier 8

这个问题有两个方面:

  • 接受任意顺序的整数

  • 返回或存储任意序列的整数

在第一种情况下,答案是使用泛型.例如:

func iterateOverInts<SeqInt: SequenceType where SeqInt.Generator.Element == Int>(xs: SeqInt) {
    for x in xs {
        print(x)
    }
}
Run Code Online (Sandbox Code Playgroud)

在第二种情况下,您需要一个类型橡皮擦.类型擦除器是一个包装器,它隐藏了某些底层实现的实际类型,并仅显示接口.Swift在stdlib中有几个,大部分都以这个词为前缀Any.在这种情况下你想要AnySequence.

func doubles(xs: [Int]) -> AnySequence<Int> {
    return AnySequence( xs.lazy.map { $0 * 2 } )
}
Run Code Online (Sandbox Code Playgroud)

有关更多on AnySequence和类型擦除器的信息,请参阅对AnySequence的一点尊重.

如果你需要协议形式(通常你不需要;你只需要使用泛型iterateOverInts),类型橡皮擦也是那里的工具:

protocol HasSequenceOfInts {
    var seq : AnySequence<Int> { get }
}
Run Code Online (Sandbox Code Playgroud)

seq必须回归AnySequence<Int>.它无法返回[Int].

你可以采用更深层次的一层,但有时它会产生比它解决的更多麻烦.你可以定义:

protocol HasSequenceOfInts {
    typealias SeqInt : IntegerType
    var seq: SeqInt { get }
}
Run Code Online (Sandbox Code Playgroud)

但现在HasSequenceOfInts有一个typealias具有所有限制意味着.SeqInt可以是任何一种IntegerType(不仅仅是Int),所以看起来就像一个约束SequenceType,并且通常需要它自己的类型橡皮擦.所以偶尔这种技术很有用,但在你的具体情况下它只能让你基本上回到你开始的地方.你不能限制SeqIntInt这里.它必须是一个协议(当然你可以发明一个协议,并使其Int成为唯一符合的类型,但这并没有太大变化).

顺便说一下,关于型号橡皮擦,你可能会发现它们非常机械.他们只是一个转发其他东西的盒子.这表明将来编译器将能够为我们自动生成这些类型擦除器.编译器已经为我们修复了其他拳击问题.例如,您以前必须创建一个Box类来保存具有通用关联值的枚举.现在,这是半自动完成的indirect.我们可以想象添加一个类似的机制,以便AnySequence在编译器需要时自动创建.所以我不认为这是一个很深的"Swift的设计不允许它." 我认为这只是"Swift编译器尚未处理它".