Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Define a Swift protocol which requires a specific type of sequence

Suppose for example we're talking about elements of type Int (but the question still applies to any type)

I have some functionality which needs to loop over a sequence of Ints. But I don't care if behind the scenes this sequence is implemented as an Array, or a Set or any other exotic kind of structure, the only requirement is that we can loop over them.

Swift standard library defines the protocol SequenceType as "A type that can be iterated with a for...in loop". So my instinct is to define a protocol like this:

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

But this doesn't work. SequenceType is not a generic type which can be specialized, it's a protocol. Any particular SequenceType does have a specific type of element, but it's only available as an associated type: SequenceType.Generator.Element

So the question is:

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

Here's some other things I've tried and why they aren't right:

Fail 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

Fail 2

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

I thought this one would work, but when I tried a concrete implementation using an Array we get

Type 'ArrayOfInts' does not conform to protocol 'HasSequenceOfInts'

This is because Array is not AnySequence (to my surprise... my expectation was that AnySequence would match any sequence of Ints)

Fail 3

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

Compiles, but there's no obligation that the elements of the sequence seq have type Int

Fail 4

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

Can't use a where clause there

So now I'm totally out of ideas. I can easily just make my protocol require an Array of Int, but then I'm restricting the implementation for no good reason, and that feels very un-swift.

Update Success

See answer from @rob-napier which explains things very well. My Fail 2 was pretty close. Using AnySequence can work, but in your conforming class you need to make sure you convert from whatever kind of sequence you're using to AnySequence. For example:

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)
        }
    }
}
like image 820
Daniel Howard Avatar asked Nov 21 '15 11:11

Daniel Howard


People also ask

What is Sequence protocol in Swift?

The Sequence protocol provides default implementations for many common operations that depend on sequential access to a sequence's values. For clearer, more concise code, the example above could use the array's contains(_:) method, which every sequence inherits from Sequence , instead of iterating manually: if bugs.

Why protocols are needed in Swift?

Protocols are used to define a “blueprint of methods, properties, and other requirements that suit a particular task or piece of functionality.” Swift checks for protocol conformity issues at compile-time, allowing developers to discover some fatal bugs in the code even before running the program.


1 Answers

There are two sides to this problem:

  • Accepting an arbitrary sequence of ints

  • Returning or storing an arbitrary sequence of ints

In the first case, the answer is to use generics. For example:

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

In the second case, you need a type-eraser. A type-eraser is a wrapper that hides the actual type of some underlying implementation and presents only the interface. Swift has several of them in stdlib, mostly prefixed with the word Any. In this case you want AnySequence.

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

For more on AnySequence and type-erasers in general, see A Little Respect for AnySequence.

If you need it in protocol form (usually you don't; you just need to use a generic as in iterateOverInts), the type eraser is also the tool there:

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

But seq must return AnySequence<Int>. It can't return [Int].

There is one more layer deeper you can take this, but sometimes it creates more trouble than it solves. You could define:

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

But now HasSequenceOfInts has a typealias with all the limitations that implies. SeqInt could be any kind of IntegerType (not just Int), so looks just like a constrained SequenceType, and will generally need its own type eraser. So occasionally this technique is useful, but in your specific case it just gets you basically back where you started. You can't constrain SeqInt to Int here. It has to be to a protocol (of course you could invent a protocol and make Int the only conforming type, but that doesn't change much).

BTW, regarding type-erasers, as you can probably see they're very mechanical. They're just a box that forwards to something else. That suggests that in the future the compiler will be able to auto-generate these type-erasers for us. The compiler has fixed other boxing problems for us over time. For instance, you used to have to create a Box class to hold enums that had generic associated values. Now that's done semi-automatically with indirect. We could imagine a similar mechanism being added to automatically create AnySequence when it's required by the compiler. So I don't think this is a deep "Swift's design doesn't allow it." I think it's just "the Swift compiler doesn't handle it yet."

like image 181
Rob Napier Avatar answered Sep 22 '22 14:09

Rob Napier