2

I want to define a protocol that defines a variable "sequence" that is of type "Collection". I want that, because i have several conforming types, that will all hold an array of different types. Example:

protocol BioSequence {
    associatedtype T
    var sequence: Collection { get }

    // Will also implement conformance to Collection and redirect all Collection functions to the property "sequence". This is because my types that conform to BioSequence are just wrappers for an array of Nucleotide or AminoAcid with some extra functionality
}

struct DNASequence: BioSequence {
    // Will hold "sequence" of type [Nucleotide]
}

struct AminoSequence: BioSequence {
    // Will hold "sequence" of type [AminoAcid]
}

Why do i want this? Because i need to implement the conformance to "Collection" only once in BioSequence and all conforming type inherit it automatically. Plus i can freely add extra functionality on the conforming types.

Now, when i try this like the code above, the compiler says: "Protocol Collection can only be used as a generic constraint". Yes, i googled what this error means, but how can i actually fix it to make my code work, like i want. Or is it not even possible to do what i want?

Thank you.

BoA456
  • 341
  • 2
  • 15
  • Is there any reason you don't simply make your structs conform to `Collection` directly? – BallpointBen Feb 07 '17 at 20:40
  • Because i will have several of those structs, and i don't want to always implement their conformance. If i implement the conformance once at the BioSequence protocol, they would always get that as well. – BoA456 Feb 07 '17 at 20:42
  • In that case you're better off declaring `protocol BioSequence: Collection` and implementing whatever you need. That way, conforming to `BioSequence` makes the structs themselves `Collection`s. If they are supposed to be `Collection`-like, better to make them actual `Collection`s instead of having a `Collection`. – BallpointBen Feb 07 '17 at 21:01
  • @BallpointBen I believe that's what he's intending to do by the comment in his example – "*Will also implement conformance to Collection and redirect all Collection functions to the property "sequence"*" – Hamish Feb 07 '17 at 21:03
  • I'm saying there's no need to redirect to an instance var when you can make the structs themselves Collections and obtain all of the functionality for free merely by conforming to the `BioSequence` protocol. – BallpointBen Feb 07 '17 at 21:06

1 Answers1

3

You can easily achieve this by using an associatedtype in your protocol, which can be constrained to Collection, allowing conforming types to satisfy the requirement with a concrete type when they adopt the protocol.

For example:

protocol CollectionWrapper : Collection {
    associatedtype Base : Collection
    var base: Base { get }
}

extension CollectionWrapper {

    var startIndex: Base.Index {
        return base.startIndex
    }

    var endIndex: Base.Index {
        return base.endIndex
    }

    func index(after i: Base.Index) -> Base.Index {
        return base.index(after: i)
    }

    subscript(index: Base.Index) -> Base.Iterator.Element {
        return base[index]
    }

    // Note that Collection has default implementations for the rest of the
    // requirements. You may want to explicitly implement them and forward them to
    // the base collection though, as it's possible the base collection implements
    // them in a more efficient manner (e.g being non random access and having
    // a stored count).
}

// S adopts CollectionWrapper and satisfies the 'Base' associatedtype with [String].
struct S: CollectionWrapper {
    var base: [String]
}

let s = S(base: ["foo", "bar", "baz"])
print(s[1]) // "bar"

Regarding your comment:

If I want to use this like that: let a: CollectionWrapper = S() [...] this leaves me with "Protocol can only be used as a generic constraint" again.

The problem is that you cannot currently talk in terms of a protocol with associated type requirements, as the types which are used to satisfy those requirements are unknown to the compiler (this isn't a technical limitation though). You can solve this by using Swift's AnyCollection type eraser to wrap an arbitrary Collection with a given element type.

For example:

let a = AnyCollection(S(base: ["foo", "bar", "baz"]))

If you need to work with additional protocol requirements from CollectionWrapper, you'll have to implement your own type eraser to do so. See Rob's answer here for an idea of how to go about this.

Community
  • 1
  • 1
Hamish
  • 78,605
  • 19
  • 187
  • 280
  • thank you for your detailed answer. If i want to use this like that: let a: CollectionWrapper = S() or let b: CollectionWrapper = AnyOtherConformingType() this leaves me with "Protocol can only be used as a generic constraint" again. I guess this is because the compiler can't figure out the runtime type of this and after all i need to go to dynamic dispatch, write these as classes and drop the protocol? – BoA456 Feb 07 '17 at 20:57
  • Ok, so i was right that the compiler doesn't know about the type. Can you edit your answer with code how the AnyCollection protocol would look like? Simply replacing Collection with AnyCollection in the protocol doesn't work at least. Thank you. – BoA456 Feb 07 '17 at 21:04
  • Thank you very much! Unfortunately this doesn't look like what i want. For example i want to write var x: BioSequence and know that only types conforming to BioSequence can be used here. Wrapping it in AnyCollection would enable a programmer to pass in other collections, wrapped in AnyCollection. Also this extra wrapping look weird. I think i will go with dropping the protocol and implementing the functionality with a superclass BioSequence and subclasses. The runtime will be able to figure out the type via dynamic dispatch and everything should be fine. Correct? – BoA456 Feb 07 '17 at 21:14
  • @BoA456 Sounds fine to me, although note that you'll lose the value semantics by going down the class route. Like I mentioned, creating your own `AnyBioSequence` type eraser is also an option (this would provide the limitation of only allowing types that conform to `BioSequence`). – Hamish Feb 07 '17 at 21:18