Search code examples
swiftswift-extensions

How to constrain function parameter's protocol's associated types


For fun, I am attempting to extend the Dictionary class to replicate Python's Counter class. I am trying to implement init, taking a CollectionType as the sole argument. However, Swift does not allow this because of CollectionType's associated types. So, I am trying to write code like this:

import Foundation

// Must constrain extension with a protocol, not a class or struct
protocol SingletonIntProtocol { }
extension Int: SingletonIntProtocol { }

extension Dictionary where Value: SingletonIntProtocol { // i.e. Value == Int
    init(from sequence: SequenceType where sequence.Generator.Element == Key) {
        // Initialize
    }   
}

However, Swift does not allow this syntax in the parameter list. Is there a way to write init so that it can take any type conforming to CollectionType whose values are of type Key (the name of the type used in the generic Dictionary<Key: Hashable, Value>)? Preferably I would not be forced to write init(from sequence: [Key]), so that I could take any CollectionType (such as a CharacterView, say).


Solution

  • You just have a syntax problem. Your basic idea seems fine. The correct syntax is:

    init<Seq: SequenceType where Seq.Generator.Element == Key>(from sequence: Seq) {
    

    The rest of this answer just explains why the syntax is this way. You don't really need to read the rest if the first part satisfies you.

    The subtle difference is that you were trying to treat SequenceType where sequence.Generator.Element == Key as a type. It's not a type; it's a type constraint. What the correct syntax means is:

    There is a type Seq such that Seq.Generator.Element == Key, and sequence must be of that type.

    While that may seem to be the same thing, the difference is that Seq is one specific type at any given time. It isn't "any type that follows this rule." It's actually one specific type. Every time you call init with some type (say [Key]), Swift will create an entirely new init method in which Seq is replaced with [Key]. (In reality, Swift can sometimes optimize that extra method away, but in principle it exists.) That's the key point in understanding generic syntax.

    Or you can just memorize where the angle-brackets go, let the compiler remind you when you mess it up, and call it day. Most people do fine without learning the type theory that underpins it.