Swift: Why Associated Types?
It's rabbit holes all the way down
Associated Types Series
In my last article I gave an incorrect explanation for why Swift has associated types. It was half-correct in that specific knowledge of the types gives the compiler the ability to optimize but that's really an orthogonal issue and a result of the way the Swift Standard Library models various concepts using associated types. My apologies! I'm going to attempt to make it up to you with a better and clearer explanation.
You can express almost anything in terms of either associated types or type parameters. You can make the types statically or dynamically known in either system. So what are the differences?
Why Associated Types
Associated types allow you to cleanly express a single concept that says something simultaneously about several independent types, the constraints on those types, and the relationships between them.
From then on we can reference the protocol and just the type members we care about.
Doing the same thing with type parameters adds at lot of verbosity. Each aspect of the concept we want to model becomes a new type parameter (probably with some constraints). These type parameters and constraints must be repeated everywhere the type parameters are used, even when we only care about one (or none) of the type parameters.
Type parameters are also inherently more fragile. Any addition of a type parameter to a protocol would immediately break all existing uses of that protocol whether the user of that protocol cares about it or not. (Touching the constraints would introduce the same problem unless the compiler could automatically propagate them.) In contrast, adding a new associated type doesn't affect existing users of the protocol at all.
Parameterize Me
Parameterized protocols can also introduce an ambiguity problem: What happens if a type adopts SomeProtocol<String>
and SomeProtocol<UITableViewCell>
?
C# solves this by requiring all but one of the protocols to be explicit and essentially hidden. You have to cast the object to the protocol to access the alternate implementation. In Swift it might look like func remove() implements SomeProtocol<UITableViewCell> { ... }
which would only be callable if you cast the object to SomeProtocol<UITableViewCell>
, while the SomeProtocol<String>
implementation would be the default visible version. Notice that the type parameter doesn't appear in the function signature and imagine the confusion that ensues as the wrong version of remove()
gets called.
The other option when something hurts is to take the doctor's advice: "stop doing that". Prohibit adopting the same parameterized protocol multiple times with varying parameters. I assume this is the path Swift would take.
Conclusion... ?
Even if Swift had parameterized protocols and no support for associated types the associated types would still be there; we'd just be representing them as type parameters instead. At the end of the day, a sequence needs to involve an element type, a generator type, an index type, and a subsequence type. Whether those types are specified as type members or type parameters doesn't change the fact that they must exist to form the complete idea of a sequence.
So the philosophical answer to the question of Why Associated Types? is that the Swift core team believes they are a better way to model concepts. They don't have the problem of multiple conformances, they encapsulate the details of a concept cleanly, and they're less fragile.
The only downside is the inability to work with existentials discussed in my last post. Assuming that restriction is lifted, what's not to like?
This blog represents my own personal opinion and is not endorsed by my employer.