I have objects that are currently initialized via one-parameter init
functions that take an Array or Dictionary.
I've realized that I can use ExpressibleByArrayLiteral
and ExpressibleByDictionaryLiteral
instead. But I believe I'll still need the non-literal initializers as well, perhaps until Passing an array to a function with variable number of args in Swift is implemented.
struct ArrayStruct: ExpressibleByArrayLiteral {
typealias ArrayLiteralElement = Int
let array: [Int]
init(array: [Int]) {
self.array = array
}
init(arrayLiteral elements: Int...) {
self.init(array: elements) // ok
}
}
struct DictStruct: ExpressibleByDictionaryLiteral {
typealias Key = String
typealias Value = Int
let dict: [String : Int]
init(dict: [String : Int]) {
self.dict = dict
}
init(dictionaryLiteral elements: (String, Int)...) {
self.init(dict: elements) // Cannot convert value of type '(String, Int)...' to expected argument type '[String : Int]'
}
}
I'd like the initializers to both call the same code path, so for the variadic init to call the collection init or vice-versa.
For the array, I can pass the Int...
variadic parameter directly to the existing [Int]
initializer. Why does this just work for array?
But this doesn't work for the Dictionary. The existing initializer is expecting a [String : Int]
dictionary, but the ExpressibleByDictionaryLiteral
function parameter is a (String, Int)...
Why do these "expressible by Array/Dictionary literal" give you a variadic parameter in the first place, rather than an array/dictionary? It's a literal array... why doesn't it give you an array?
How can I convert (String, Int)...
to [String : Int]
?
The problem is that there is no "obvious" conversion from a list of key-value pairs to a Dictionary. You must decide how to deal with duplicate keys. Are they a programming error, causing an crash? Do you take the first one? The last one? Combine them somehow? It's up to you to decide and Swift supports all of these approaches.
For example, to crash the program if the keys are not unique (this is not a bad idea for literals), you would write this:
init(dictionaryLiteral elements: (String, Int)...) {
let dict = Dictionary(uniqueKeysWithValues: elements)
self.init(dict: dict)
}
Alternately, to take the last element for a given key, you would write this:
init(dictionaryLiteral elements: (String, Int)...) {
let dict = Dictionary(elements, uniquingKeysWith: { _, second in second })
self.init(dict: dict)
}
But you could also accumulate the sum of all duplicate keys:
init(dictionaryLiteral elements: (String, Int)...) {
let dict = Dictionary(elements, uniquingKeysWith: +)
self.init(dict: dict)
}