I can't. Sometimes think I fully understand it, especially with some help, like here, at least for a while, until I slip back into being confused again, and need to repeat some exercise like this, and so on ...
Now for example, I cannot easily answer @Lantua's follow up question, at least not without giving it a lot of time and effort, and I'm beginning to doubt my explanation above, or wonder if there might be some bug somewhere that confuses us, either way, it's complicated.
In my day to day code, which happens to be mostly library-like in-house code where performance matters (graphics, geometry, physics, ...), I've adopted the following rule to be able to reason about my code:
- Never implement protocol requirements in protocol extensions.
So I only implement protocol requirements in conforming types (that would be X
, Y
and Z
here). And the members I add in protocol extensions are never requirements.
The resulting subset of Swift's protocols and generics is enough for most of what I do, and the most reoccurring serious limitation I face is one which I think has nothing to do with limiting myself to my subset.
Namely this.
While it is possible to write specific implementations that are more efficient for some generic type parameters, these faster implementations will only be available to call sites that are in the same generic context. This is a problem if you're trying to write code that is both reusable and efficient ... Let me explain by example instead:
- We have a protocol P with associated type
A
. - In an extension to
P
we have a (non-requirement) methodbar
that works for allA
. - We've also implemented a faster
bar
whereA: Q
. - Now, we want to add methods
baz
,qux
,grault
etc, all usingbar
. - We are faced with the following AFAICS 3 options:
- Implement all of them once, for any
A
, meaning all of them will have to use the general (slow)bar
. (dynamic cast is not an option, too slow, because methods are called with high frequency.) - Implement all of them twice, once for the general case and once for the case where
A: Q
, and make sure that whatever future methods we write that happens to use one of these methods are also implemented for both cases ... This doesn't scale. - Jump through some hoops where we effectively access the efficient implementations via some required static method available via concrete
A
s, This can be done via someEfficientBarSupporting
protocol with a requirement that conformingA
-types implements. ... It's too complicated to explain clearly here, but it kind of works, and is much too complicated to feel nice.
- Implement all of them once, for any