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

时间:2015-11-21 11:55:45

标签: swift generics protocols

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

我有一些功能需要遍历一系列Ints。但我不在乎,如果在幕后将这个序列实现为数组,或者Set或任何其他异乎寻常的结构,唯一的要求就是我们可以遍历它们。

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

protocol HasSequenceOfInts {
    var seq : SequenceType<Int> { get }
}

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

所以问题是:

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

以下是我尝试的其他一些事项以及为什么他们不对:

失败1

protocol HasSequenceOfInts {
    var seq : SequenceType { get }
}
  

协议&#39; SequenceType&#39;只能用作通用约束   因为它有自我或相关的类型要求

失败2

protocol HasSequenceOfInts {
    var seq : AnySequence<Int> { get }
}
class ArrayOfInts : HasSequenceOfInts {
    var seq : [Int] = [0,1,2]
}

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

  

输入&#39; ArrayOfInts&#39;不符合协议&#39; HasSequenceOfInts&#39;

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

失败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的数组,但是我没有充分的理由限制实现,这感觉非常迅速。

更新成功

见@ 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)
        }
    }
}

4 个答案:

答案 0 :(得分:8)

这个问题有两个方面:

  • 接受任意顺序的整数

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

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

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 { $0 * 2 } )
}

有关AnySequence和类型 - 橡皮擦的更多信息,请参阅A Little Respect for AnySequence

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

protocol HasSequenceOfInts {
    var seq : AnySequence<Int> { get }
}

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

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

protocol HasSequenceOfInts {
    typealias SeqInt : IntegerType
    var seq: SeqInt { get }
}

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

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

答案 1 :(得分:2)

(经过测试并在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]的数组,或将其分配给变量(或基本上对其执行任何操作),则会收到一条错误消息,提示

  

协议“ HasSequenceOfInts”仅具有通用或相关类型要求,因此只能用作通用约束

现在是有趣的部分了。

我们将创建另一个协议HasSequenceOfInts_(随意选择一个更具描述性的名称),该协议将不具有关联的类型要求,将自动被{{ 1}}

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_,然后访问{{1} }属性(注意:,因为允许函数重载,所以如果您使用函数HasSequenceOfInts而不是实例变量,则可以使用相同的名称,并且可以使用):

seq_

与接受的答案相比,这需要更多的设置,但是这意味着您不必记住将返回值包装在实现协议的每种类型的seq()中。

答案 2 :(得分:1)

我相信你需要将它的要求降低到只有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

在斯威夫特目前的状态下,你可能无法做到你想要的,也许在未来。

答案 3 :(得分:0)

这是Daniel Howard要求的非常具体的例子

1)符合SequenceType协议的类型几乎可以是任何序列,即使Array或Set都符合SequenceType协议,它们的大多数功能都来自CollectionType上的继承(符合SequenceType)

丹尼尔,在你的游乐场尝试这个简单的例子

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库中“免费”使用的功能吗?
  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>>')
    
相关问题