定义需要特定类型序列的 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 数字流。在你尝试实施之前,你必须回答自己几个问题

  1. 我可以重用标准 Swift 库中 'for free' 的某些功能吗?
  2. 我是否在尝试模仿 Swift 不支持的某些功能? Swift 不是 C++,Swift 不是 ObjectiveC ... 我们在 Swift 之前使用的很多结构在 Swift.
  3. 中没有等价物
  4. 以我们可以理解您的要求的方式定义您的问题

您在找这样的东西吗?

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 输入你实现协议的地方。