2

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).

BallpointBen
  • 9,406
  • 1
  • 32
  • 62

1 Answers1

3

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.

Rob Napier
  • 286,113
  • 34
  • 456
  • 610
  • 1
    _"There is a type `Seq` such that `Seq.Generator.Element == Key`..."_, possibly worth being abundantly clear in this good thorough answer, mentioning explicitly that _"there is a `Seq` **that conforms to `SequenceType`** such that ..."_, even if `Seq.Generator.Element` kind of implies this. – dfrib Mar 30 '16 at 22:07
  • Just to be clear, the compiler can infer the type from the argument right? You can just write `let a: [String] = []; let d = Dictionary(a)` and it will automatically convert it to `let d = Dictionary(a)`? – BallpointBen Mar 30 '16 at 22:59
  • @James I don't see how you'd get [String:Int] out of that example, but yes, the compiler will try to infer the type at compile time. If it cannot decide on a single, best type then it'll give you an ambiguous type error. The key is that the result must have exactly one type. You just might have to write it down. – Rob Napier Mar 31 '16 at 02:09
  • Ah, I think I see what you wanted. No, the compiler can't know that Value is Int. There might be other types that conform to SingletonIntProtocol. The Swift compiler today is not powerful enough to prove otherwise, even if it can see all the relevant code. I do expect the compiler to fix the "makes type non-generic" error in the near future, which would allow you to get there with the protocol. (Have you tried replacing Value: SingletonIntProtocol with Value == Int? I thought that already worked.) – Rob Napier Mar 31 '16 at 02:14
  • 1
    Unfortunately, no: `Same-type requirement makes generic parameter 'Value' non-generic` – BallpointBen Mar 31 '16 at 03:12
  • For Swift 3.x the recommended Swift-4.0 compatible way to write this is: `init(from sequence: Seq) where Seq.Iterator.Element == Key {` Three changes: 1) SequenceType -> Sequence 2) Generator -> Iterator 3) where clause goes on function definition instead of on the generic parameter itself – Ryan May 03 '17 at 17:42