Imagine that you have a set of integers and you want to override the default implementation of CustomStringConvertible for a Set to use set-builder notation. You might try to do this with an extension like the following:
extension Set: CustomStringConvertible where Element == Int {
var description: String {
let values = self.map({ "\($0)" }).joined(separator: ", ")
return "{\(values)}"
}
}
Unfortunately, this fails to compile with the following error:
Conformance of 'Set' to protocol 'CustomStringConvertible' conflicts with that stated in the type's module 'Swift' and will be ignored; there cannot be more than one conformance, even with different conditional bounds
Well, it looks like that won't work because Set already conforms to CustomStringConvertible.
Okay, fine. We can still provide our own implementation of description without declaring a (conflicting) conformance to CustomStringConvertible:
extension Set where Element == Int {
var description: String {
let values = self.map({ "\($0)" }).joined(separator: ", ")
return "{\(values)}"
}
}
Now the compiler's happy, so let's kick the tires a bit:
let set: Set<Int> = [7, 3, 15, 31]
String(describing: set) // prints "[7, 3, 15, 31]"
Wait, what? Where are our curly braces?
set.description // prints "{7, 3, 15, 31}"
Oh, there they are. Hmm…
I know that static dispatch is used to call methods that are defined in a protocol extension rather than in the protocol itself, but description is defined in the original CustomStringConvertible protocol, so shouldn't init(describing:) use dynamic dispatch to call our own custom implementation?
All hope is not lost yet, though. We can also try defining an empty SetBuilderNotationRepresentable protocol:
protocol SetBuilderNotationRepresentable: CustomStringConvertible {}
Next, we'll make sets of integers conform to this new protocol:
extension Set: SetBuilderNotationRepresentable where Element == Int {}
Finally, we'll provide our own implementation of init(describing:):
extension String {
init<Set>(describing instance: Set) where Set: SetBuilderNotationRepresentable & Sequence {
let values = instance.map({ "\($0)" }).joined(separator: ", ")
self.init("{\(values)}")
}
}
Now all that's left is to cross our fingers, and…
String(describing: set) // prints "{7, 3, 15, 31}"
Success!! ![]()
In all seriousness, though, this seems a bit excessive just to replace a pair of square brackets with curly brackets. Am I missing something here, or is this really the most "elegant" way to provide our own implementation of CustomStringConvertible for a collection?