I think the proposal makes sense, because it makes sense for a protocol to have what the proposal calls "primary associated type", but I don't think that the "Require associated type names" alternative is actually an alternative to this: the two complement each other perfectly, and I'd love to see them in the same proposal. Let me elaborate on this.
Both generic parameters and associated types suffer, in my opinion, of a potential problem of clarity at the usage site. Types like Array<Element>
or Dictionary<Key, Value>
, when specialized, are generally pretty clear: Array<Int>
means "an array of ints". But many generic types are not like that, and even something as simple as Result
could be potentially confusing. Consider for example:
// somewhere in the codebase
extension String: Error {}
// somewhere else
let someResult: Result<Int, String> = ...
after using Result
for a while, one understands that the first generic parameter is the Success
type, and the second is the Failure
, but by just reading Result<Int, String>
it's not clear which is which (in fact, many languages that have a similar type in their stdlib consider the second parameter as the success one).
I think it would be great if I could write, for example, Result<Int, .Failure = String>
.
Let's consider a couple of libraries from the Pointfree people: swift-tagged and swift-composable-architecture.
swift-tagged
is based on the very useful Tagged<Tag, RawValue>
type, that can be used to replace a plain raw value with a new one that carries some type information, in order to write better self-documenting code. But the meaning and positioning of the generic parameters can be confusing. A usage example is the following:
struct User {
let id: Tagged<User, Int>
}
what's the RawValue
of id
? it's User
or Int
? I think it would be clearer if one could write something like let id: Tagged<Int, .Tag = User>
.
swift-composable-architecture
is based on the Reducer<State, Action, Environment>
type, where each generic parameter represents a specific aspect of that particular Reducer
. Unfortunately, when reading something like:
let x: Reducer<Int, String, [String: Int]>
it's impossible to understand what's what. It would be great if we could specify which type parameter we're specializing with which type.
This kind of reasoning applies also to protocols, and the examples in the pitch clearly show this. If I see Collection<String>
in a type constraint, I immediately understand that I'm dealing with a collection of String
; but there would be no point in having the Index
associated type as primary, because for example Collection<String, Int>
is simply confusing. But a Collection<String, .Index == Int>
as type constraint would be perfect.
The distinction between primary and non-primary, when it comes to both associated types and type parameters, is very useful in my opinion, and having the option to write Collection<String>
opens the possibility of using the angle brackets syntax for more stuff, like the very nice, I think, .Index == Int
declaration, without the need to add anything extra for the primary types, precisely because they're primary. I think this approach would solve the drawbacks listed in the pitch, and I don't think it goes against SE-0081 that much, because the point is rendering the simplest most common case as concise and clear as possible, and progressively introducing more constraints as needed, eventually turning to the where
clause in cases where a list of type constraints is sufficiently heavy to justify a change in code style.