This pitch floats the idea of giving protocols where clauses and angle brackets, in a similar fashion to how generics use them.
Motivation
This idea came up when I was trying to use a protocol type for a function's parameter. Normally, this is how one does it:
func function1(foo: SomeProtocol) {
// do something here
}
However, if the protocol has associated types, then it can not be used as the parameter's type directly. The solution is to use generics:
func function2<T: SomeProtocol>(foo: T) {
// do something here
}
And one step further to add constraints to the protocol's associated types:
func function3<T: SomeProtocol>(foo: T)
where T.Type1 == SomeType, T.Type2 = AnotherType {
// do something here
}
The above works, but with a few problems:
-
foo
is ofSomeProtocol
type infunction1
, but not infunction2
andfunction3
. - If
function3
doesn't need to know the "underlying" type of foo, then it's a somewhat long-winded way of expressing a simple idea. This also renders the genericT
practically unused. - If
function2
andfunction3
don't need to know the "underlying" type of foo, then by using generic, they break the visual consistency fromfunction1
, and make code readers think unnecessarily in terms of generics instead of protocols.
Proposed Solution
1. Give protocols where clauses.
Where clauses are applied directly after protocols to constrain their associated types.
// similar to function3, but doesn't expose the parameter's underlying type
func function4(foo: SomeProtocol where Type1 == SomeType && Type2 == AnotherType) {
// ...
}
function4
can be synthesized by the compiler as function5
:
func function5<T: SomeProtocol>(foo: T)
where T.Type1 == SomeType, T.Type2 = AnotherType {
foo = foo as! SomeProtocol
// ...
}
2. Give protocols angle brackets.
Angle brackets can be used when there is only 1 associated type in a protocol.
func function6(foo: SomeProtocol<SomeType>) {
// ...
}
function6
can be synthesized by the compiler as function7
:
func function7<T: SomeProtocol>(foo: T) where T.Type == SomeType {
foo = foo as! SomeProtocol
// ...
}
An Example
// print any sequence of integers
// could be a set or dictionary keys
// or any type that conforms to Sequence and has Int as associated type
func printIntegers(in integerSequence: Sequence<Int>) {
for integer in integerSequence {
print(integer)
}
}
A Side Note
There is a syntactical difference between a protocol where clause and a generic where clause. A protocol where clause uses &&
instead of ,
between constraints. I will make a separate pitch to suggest allowing logic operators such as &&
, ||
, and !
in where clauses.
EDIT
found a related discussion