Jordan,
Your comments brought up a few more closely related ideas that have been
bubbling under.
To everyone,
Sorry for going beyond topic here. The discussion of the further proposals
below should be taken into their own threads if there's interest. I'm just
trying to motivate what else moving out the `where` clause would make possible.
1. My counter-proposal against making generic parameters public
> Another factor here is that we've been planning for a while for generic
> parameters to types to be exposed as implicit member typealiases, since they
> already conflict with the namespace for member typealiases. Therefore it's
> important to name generic parameters to types well, but less important to do
> so for generic parameters for functions. (They're also much less likely to be
> ad hoc for types; there has to be something that describes the relation
> between the Self type and the parameter, while the function might not have
> anything more interesting than "operand".)
I disagree with that. I think it's more natural to restrict generic type
parameters to the immediate local scope of the class/struct/enum definition or
extension, and simply allow making the type public with `typealias`. For
example, this would make `Element` a public member of `Array`:
public enum Result<T, Error> {
public typealias Value = T // This makes `Value` public for extensions
and everyone
}
I would even allow publishing the otherwise local name by repeating it in
`typealias` like this:
public struct Array<Element> { // This `Element` is only available in (the
missing) where clause and the scope below.
public typealias Element = Element // This line makes `Element`
available everywhere, see below.
}
extension Array<T> { // <- Regardless of the pattern (`T`) used here,
var mid: Element? { // the type `Element = T` is available here
return ... // because it was made public elsewhere.
}
}
extension Array<Optional<T>> { // <- An example of pattern matching (see
further below).
// Ditto, `Element = T?`
}
typealias Ints = [Int]
let x: Ints.Element = ... // Int
Next, I propose how to extend this syntax with pattern matching. The above
thinking is a natural progression from the use of pattern matching for
introducing generic type parameters in type `extension`s.
2. Proposal to enable pattern matching of generic types in generic parameters
> This is also a minor point against declaring generic parameters for
> extensions, since they would have to match up. We also do need a syntax some
> day to represent new parameters:
>
> // For expository purposes only:
> extension <T> Array where Element == Optional<T>
>
> But that deserves its own proposal. I just don't want us to paint ourselves
> into a corner.
I agree that we need that feature. But instead of your proposed syntax, I'd
take influence from the already existing pattern matching that we have at value
level, such that:
1. any generic type expressions within the angle brackets `<...>` are taken as
patterns to match against (e.g. `Array<Element>`, `Optional<Foo>`,
`Dictionary<S, S>`), and
2. all non-generic type identifiers in those expressions are taken as generic
parameters and not concrete types (`Element`, `Foo`, or `S`, respectively).
Your example would translate into the first of these two:
extension Array<Optional<T>> { // extending [T?] doesn't even need any
`where` clause
func unwrapAll() -> [T] { ... }
}
extension Dictionary<K, Dictionary<K, V>> { // This pattern match requires
the same `K` twice.
subscript(x: K, y: K) -> V { ... }
}
The generic parameters would shadow any existing names in the outer scope, i.e.
`extension Array<String>` would mean the same as `extension Array<Element>`.
But it would be a good idea to add a warning (or linter error) at least when
stdlib types such as `Swift.String` are shadowed like that. I think the
benefits from pattern matching outweigh the possible confusion, especially
since you can't use any members of `String` if you mistakenly wrote `extension
Array<String> { ... }` instead of `extension Array<T> where T == String`.
With this syntax, we could also allow extending Array, Dictionary, and Optional
in their more natural notation:
extension [T?] {
func unwrapAll() -> [T] { /* ... */ }
}
extension [K: [K: V]] {
subscript(x: K, y: K) -> V { /* ... */ }
}
Here are a few more (somewhat realistically needed) examples with further
`where` clauses:
extension [T] where T : Hashable { /* ... */ }
extension T? where T : Comparable { /* ... */ }
extension [K: V] where V : Equatable { /* ... */ }
extension [T] where T == String { /* ... */ }
I think pattern matching is a very expressive, intuitive, and readable
technique that works quite well for this purpose, better than what we currently
have.
3. Future directions
Brent already pointed out that the `where` constraint syntax could be used for
dependent types (i.e. type-level values as generic parameters). Four more
possible directions come to my mind:
(1) Adding conditional protocol conformances:
extension [T]: Equatable where T : Equatable { /* ... */ }
extension [T]: Comparable where T : Comparable { /* ... */ }
extension [K, V]: Equatable where V : Equatable { /* ... */ }
extension Foo<X, Bar<Y>> : Bazzable where X : Baz, Y : Baz { /* ... */ }
(2) Extending non-nominal types:
extension (A, B) { var tail: B { /* ... */ }}
extension (A, B, C) { var tail: (B, C) { /* ... */ }}
(3) Using variadic patterns (shamefully borrowing the `...` notation from C++
here to mean an arbitrary-length list of patterns to match or expressions to
expand):
extension (T...) : Equatable where T : Equatable... {}
func == <T...>(lhs: (T...), rhs: (T...)) -> Bool where T : Equatable... {
/* ... */ }
(4) Since all type constraints with `:` fly out to of the generic parameter
list into the `where` clause, we could enable the use of colons to give labels
to generic parameters (and thus even make some of them have default values):
struct Parser<encoding: Encoding, input: Input>
where Encoding : EncodingProtocol /*, ... */
{
// ...
}
let parser = Parser<encoding: UTF8, input: String>()
— Pyry
_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution