Many of Jordan’s regrets are noted as such because they’re too late to change without causing other problems—hence, “regrets” and not “wishes” or “to-dos.” That we should have done it in Swift 1 or 3 with good reason does not mean that we should or even could still do this now. Indeed, my presumption with Jordan’s excellent regrets series is that there are multiple reasons why these issues are too late to change for Swift.
I’m not certain that the syntax you propose is more consistent or simple. It has fewer punctuation marks, but that does not necessarily equate to consistency or simplicity.
Consider that we have a special rule where the generic parameter can be omitted inside the extension of a generic type. Therefore, with your proposed syntax:
extension Dictionary where Value: Optional {
// . . .
}
extension Optional {
func f<T>(_: T) where T: Dictionary, T.Value == Optional {
// How do you teach why we use “==” here but “:” above?
}
func g<U>(_: U) where U: Dictionary, U.Value: Optional {
// The distinction between “f” and “g” is subtle yet crucial.
// Currently, users can’t confuse them, but with your syntax the compiler can’t save the user from that confusion.
}
func h<V, W>(_: V, _: W) where V: Dictionary, V.Value == Optional, W: Dictionary, W.Value: Optional, W.Value.Wrapped: Dictionary, W.Value.Wrapped.Key == . . . {
// You get my point.
}
}
If you are solving the same problem with “less” syntax, then it stands to reason that there has got to be more subtlety in the syntax you do use, which may be less consistent or less simple to understand.
That use of the bare generic type within an extension, where it is implicitly treated as Self, is something that Slava Pestov among others has mentioned as worth removing from the language as a breaking change in Swift 6.
The confusion you describe is due to the inconsistency of that existing behavior, not the feature I propose.
The spelling I propose is consistent with every other place in the language where we specify a type being part of a named set of types. Constraints for conformance to a protocol, and subclasses of class, are both spelled with a colon, and it would be consistent therewith to use the same spelling for generics.
You’re proposing something for Swift, the language which currently behaves in the way that’s outlined. We don't have a blank canvas to design a new language, and “fit” is a key criterion on which proposals are judged.
If a prerequisite for "consistency" is undoing one of Jordan's "Swift regrets" plus one of Slava's "Swift regrets"—to a degree at least you're designing for a different language than the one we have.
Let's not conflate sameness with consistency. Consistency would have similar things treated similarly and different things treated differently. And this brings me to an issue here, because what you've done is treated two different things the same way—leading to inconsistency:
struct S<T> { }
func e<T, U>(_: T) where T: Collection, T.Element == S<U> { print(T.self) }
e([S<Int>()]) // prints "Array<S<Int>>"
// You propose using `:` to denote a "named set of types",
// so that the existing function above can be rewritten:
func e<T>(_: T) where T: Collection, T.Element: S { print(T.self) }
// So far workable, with only the drawback that `e` has two generic
// parameters but only one is visible with your proposed syntax.
// ---
class C<T> { }
class D<T>: C<T> { }
func f<T, U>(_: T) where T: Collection, T.Element == C<U> { print(T.self) }
func g<T, U>(_: T) where T: Collection, T.Element: C<U> { print(T.self) }
// Note the difference between `==` and `:` here:
f([D<Int>()]) // prints "Array<C<Int>>"
g([D<Int>()]) // prints "Array<D<Int>>"
// With your proposed shorthand:
func f<T>(_: T) where T: Collection, T.Element: C { print(T.self) }
func g<T>(_: T) where T: Collection, T.Element /* ??? */
With C, there are two "named sets of types"—the generic parameter, and the subclassing hierarchy. By proposing to use the same notation : for these two unrelated sets, you're now unable to distinguish them when the distinction matters.
You can forbid the use of your shorthand for f and say that it actually behaves like g, but then that'd be different from how the same syntax works for structs (inconsistency! subtlety!); or you can make your shorthand work like f and then...not have an equivalent syntax for g (inconsistency! subtlety!).
The constraint “where T: C” includes subtypes just as it does today for concrete classes and protocols. Specifically, “where T: C” means “T is C with any generic parameters, or a subclass thereof.”
If people find it useful to be able to specify, “T is exactly C with any generic parameters, not one of its subclasses” then that could be proposed as well. The spelling “where T == C” seems natural enough for that situation, and is purely additive to the feature I propose.