the protocol exists to mitigate proliferation of generic parameters, for example:
struct BranchBuffer<Element> where Element:BranchElement
{
}
instead of
struct BranchBuffer<Key, Heads, Divergence> where Heads:BranchElementHeads, Divergence:BranchElementDivergence
{
}
which eventually leads to fake empty enumeration types, whose only purpose is to hold “real” types witnessing the associatedtype requirements in the protocol.
The main reason to not do this is that you will hurt Swift's ability to infer types since there is no way to know what Element is given Key, Heads and Divergences.
For example, presumably the BranchElement conformances actually hold onto some data, like this:
struct BranchBuffer1<Element> where Element: BranchElement {
let key: Element.Key
let heads: Element.Heads
let divergences: Element.Divergences
}
With this style you have no choice but to specify the generic on BranchBuffer1 when creating it:
let buffer1 = BranchBuffer1(
key: 1,
heads: Fake.Heads(),
divergences: Fake.Divergences()
) // 🛑 Can't infer Element
let buffer1 = BranchBuffer1<Fake>(
key: 1,
heads: Fake.Heads(),
divergences: Fake.Divergences()
) // âś… Element generic is required
Whereas if you do it in the simpler style, with a generic for each associated type:
struct BranchBuffer2<Key, Heads, Divergences>: BranchElement
where
Heads: BranchElementHeads,
Divergences: BranchElementDivergence
{
let key: Key
let heads: Heads
let divergences: Divergences
}
…then you get to create a BranchBuffer2 without specify the generics at all:
IMO, when introducing a new protocol, it is helpful to ask "which generic algorithms can I write using this?"
If there can be some interesting relationship between those associated types which enables algorithms to be written, or if each of those non-constructible types has some unique semantic meaning, I think it can be an appropriate design. If it is quite literally only to save some typing and has no semantic meaning at all, then it may be more difficult to justify.
It is difficult to give a blanket answer that something is always "good form" or "bad form"; it depends on what you're trying to achieve.
when i introduce a new protocol, it is most often because i have a generic type and i want to make the generic type conform to a different protocol, but i cannot do so without running into the “multiple conditional conformances” problem.