首页 文章

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

提问于
浏览
12

例如,假设我们讨论的是Int类型的元素(但问题仍然适用于任何类型)

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

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

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

但这并不是一个协议 . 任何特定的SequenceType都具有特定类型的元素,但它仅作为关联类型提供:SequenceType.Generator.Element

所以问题是:

How can we define a protocol which requires a specific type of sequence?

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

Fail 1

protocol HasSequenceOfInts {
    var seq : SequenceType { get }
}

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

Fail 2

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

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

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

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

Fail 3

protocol HasSequenceOfInts {
    typealias S : SequenceType
    var seq : S { get }
}

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

Fail 4

protocol HasSequenceOfInts {
    var seq : SequenceType where S.Generator.Element == Int
}

不能在那里使用where子句

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

Update Success

请参阅@ 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

    这是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数据流 . 在你尝试实施某些事情之前,你必须回答几个问题

    • 可以重用一些在标准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>>')
    
  • 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
    

    在Swift目前的状态下,你可能无法做到你想要的,也许在未来 .

  • 2

    这个问题有两个方面:

    • 接受任意顺序的整数

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

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

    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 .

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

    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 ,并且通常需要它自己的类型橡皮擦 . 所以偶尔这种技术很有用,但在你的具体情况下它只能让你基本上回到你开始的地方 . 你不能在这里约束 SeqIntInt . 它必须是一个协议(当然你可以发明一个协议,使 Int 成为唯一符合标准的类型,但这并没有太大变化) .

    顺便说一句,关于类型擦除器,你可能会看到它们只是一个转发给其他东西的盒子 . 这表明将来编译器将能够为我们自动生成这些类型擦除器 . 编译器已经为我们修复了其他拳击问题 . 例如,您以前必须创建一个 Box 类用于保存具有通用关联值的枚举 . 现在用 indirect 半自动完成 . 我们可以想象一个类似的机制被添加到自动创建 AnySequence 时's required by the compiler. So I don'认为这是一个很深的"Swift's design doesn't allow it."我认为它只是"the Swift compiler doesn't handle it yet."

  • 8

    (在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'只能用作通用约束,因为它具有Self或相关类型要求

    有趣的来了 .

    我们将创建另一个协议 HasSequenceOfInts_ (随意选择更具描述性的名称) which will not have associated type requirements, and will automatically be conformed to by 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_ 属性( note: ,因为允许函数重载,如果您创建了一个函数 seq() 而不是实例变量,你可以给它相同的名称,它会工作):

    let a: HasSequenceOfInts_ = ArrayOfInts()
    a.seq_
    

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

相关问题