[swift-evolution] [swift-evolution-announce] [Review] SE-0089: Replace protocol<P1, P2> syntax with Any<P1, P2>
Joe Groff
jgroff at apple.com
Tue May 24 14:07:05 CDT 2016
'Any' is definitely a better name, but I think this is still syntax only a compiler can love. If we're going to repaint this bikeshed, I think we should also consider as an alternative some form of infix syntax for composing constraints. Rust uses `P1 + P2`, and various C++ proposals have suggested `P1 && P2`. Some pros(+) and cons(-) I can see with this approach:
+ Infix notation works not only in type position, but constraint position as well, providing a nicer way to express multiple type variable constraints:
var x: P && Q
func foo<T: P && Q, U>(x: T)
+ Infix notation feels subjectively lighter, being less nesty and angle-bracket-blinding. Compare the above with:
var x: Any<P, Q>
func foo<T: Any<P,Q>, U>(x: T)
Particularly in the second declaration, I find the nested angle brackets and comma delimiters to be hard to visually parse.
± Like 'Any', an infix operator doesn't pass judgment on what kinds of constraint is being applied, so it naturally extends to expressing class-with-protocol constraints.
- Infix notation doesn't provide an obvious place for generalized existentials to hang secondary constraints. The angle brackets for Any provide an enclosed syntactic space we can easily stuff a 'where' clause:
func sum(_ collection: Any<Sequence where Element == Int>) -> Int { ... }
(though this notation still feels heavy and awkward to me).
- The bracketing of `Any` might let us address the curious case of protocol vs existential metatypes in a better way. Right now, the static metatype for a protocol type (the type of `P.self`) is spelled `P.Protocol`, and the dynamic metatype for any type conforming to the protocol (the erased type of `T.self` where T: P) is spelled `P.Type`. Unintuitively, when a protocol type is substituted into `T.Type` in a generic context, you get the static type `P.Protocol` rather than `P.Type`, for soundness reasons:
func staticType<T>(of _: T) -> T.Type { return T.self }
This substitution behavior could be made clearer if we moved a dynamic metatype's `.Type` into the Any brackets, so that `Any<P.Type>` would be the dynamic metatype of any type conforming to P, and `Any<P>.Type` would be the static metatype of the type `Any<P>`. Infix notation doesn't provide an opportunity to make this clarification.
- A few people have also noted the similarity between Any<...> and normal generic types. This is potentially confusing today, but with enough magic language features from the future, Any *could* conceivably be made a library feature in the fullness of time:
// Let's say 'protocol' constraints indicate second-order constraint variables:
enum Any<Constraints: protocol> {
// And we have GADTs:
case value<T: Constraints>(T)
}
// And we have user-defined value subtyping:
extension <Constraints: protocol, T: Constraints> T: Any<Constraints> { ... }
-Joe
More information about the swift-evolution
mailing list