In an article yesterday, I mentioned that I wasn’t having much luck extending Dictionary
to give it an initializer that took a sequence of tuples, because my attempt wasn’t compiling, even though I was sure that it should:
extension Dictionary { init<S: Sequence where S.GeneratorType.Element == Element> (_ seq: S) { self.init() for (k,v) in seq { self[k] = v } } }
Looked like the problem was with destructuring a typealiased tuple type into its consitutents. Like a good little beta tester, I settled in this morning with a fresh pot of coffee to file a bug report. My plan was to reproduce the problem without the Dictionary
part, to try and clarify exactly where the issue lay. I tried writing a couple of classes that used typealiased tuples, one taking two generic placeholders and typealiasing the pair of them as a tuple, and then writing a function on one that took the other.
After about 20 minutes plugging away at this, I was still unable to reproduce it independently, and was geting quite frustrated. Then I realized hang on, what made me so sure this was a problem with tuples?1 I pushed aside my failed attempt and just tried compiling this, and got the exact same error:
extension Array { func someFunc<S: Sequence where S.GeneratorType.Element == Element> (_ seq: S) { for e in seq { } } }
Well, shit. That’ll teach me to overthink a problem. I scold myself for not applying Occam’s razor and assuming the cause was more complicated than it was.2 Once I stopped obsessing over the tuple part, the next step in fixing my code became obvious. for ... in
is just shorthand for creating a generator and then looping while next()
returns non-nil
elements. So I tried this:
extension Array { func someFunc<S: Sequence where S.GeneratorType.Element == Element> (seq: S) { var gen = seq.generate() let elem = gen.next() } }
Bingo! The call to gen.next()
reports Cannot convert the expression's type 'Self.Element? to type 'Self.Element?'
. Somehow type inference is borked. But if I qualify the declaration of elem
as elem: Element?
it works fine.
So now I know how to work around it, I can finally write my sequence initializer for dictionary.
But first: @rbrockerhoff replied yesterday with a gist that was very close to the Sequence
initializer (it took MapCollectionView
as an argument rather than the sequence directly, which somehow avoids the type inference issue in a similar same way that my version that took an Array
did).
As part of this, he did something very smart, which is to separate out inserting the sequence into the dictionary into another method, and then calling it from init
instead of having the loop in init
itself. This is a great example of solving something generically first, and then specializing your generic solution to the immediate problem. So here’s the full solution:
extension Dictionary { init<S: Sequence where S.GeneratorType.Element == Element> (_ seq: S) { self.init() self.merge(seq) } mutating func merge<S: Sequence where S.GeneratorType.Element == Element> (seq: S) { var gen = seq.generate() while let (k: KeyType, v: ValueType) = gen.next() { self[k] = v } } } let pairs = enumerate(["zero","one", "two"]) let d: [Int:String] = Dictionary(pairs) d[1] // returns {Some "one"}... yes!
Just one wrinkle – you still have to give the dictionary variable a type, it won’t infer it. I’m not sure if this is a bug in my code, a Swift bug, or a feature not a bug. If you know, let me know.
- I was possibly nudged towards the tuple assumption because of another bug in playgrounds with mapping arrays of tuples. ↩
-
I did the same thing yesterday when I convinced myself the only way to write my own
Dictionary.init()
was to create another dictionary and assign it toself
, before a helpful comment from @NachoSoto suggested I just tryself.init()
. Duh. ↩
[…] found it! Read the next post for a […]