[Pitch] Moving where Clauses Out Of Parameter Lists

I think the where clause should be moved to the end as you outlined an any
constraints beyond simple statements of conformance to a protocol or
superclass should only be allowed in the where clause. If someone desires
to state simple conformance/superclass for a generic parameter they should
be allowed to do so in either location.

···

On Wed, Apr 6, 2016 at 1:21 PM Pyry Jahkola via swift-evolution < swift-evolution@swift.org> wrote:

Joe Groff wrote:

It's a judgment call. It's my feeling that in many cases, a generic
parameter is constrained by at least one important protocol or base class
that's worth calling out up front, so it's reasonable to allow things like
'func foo<C: Collection>(x: C) -> C.Element' without banishing the
'Collection' constraint too far from the front of the declaration.

Fair enough. I think it would be clearer if all the constraints appeared
in one standard place (in the `where` clause).

— Pyry
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

It's probably appropriate. Given that, in this example, Bar implicitly
unifies its generic constraint T with Foo's associatedtype T (or,
typealiases T with T), you need to be at least a little careful how you
name your generic constraints.

protocol Foo
{
    associatedtype T
}

struct<T> Bar : Foo
{
}

···

On Thu, Apr 7, 2016 at 12:52 AM, Erica Sadun via swift-evolution < swift-evolution@swift.org> wrote:

On Apr 6, 2016, at 5:45 PM, David Waite via swift-evolution < > swift-evolution@swift.org> wrote:

On Apr 6, 2016, at 1:36 PM, Joe Groff via swift-evolution < > swift-evolution@swift.org> wrote:

I think this is a good idea, though I would put the `where` clause after
the function signature:

func foo<T: Foo, U: Bar>(x: T, y: U) -> Result<T,U>
   where T.Foo == U.Bar /*, etc. */
{
}

A bit of a meta-argument:

It is very common to use single-capital letter generic parameters, and the
API style does not give guidance around the naming of generic parameters.

However in my humble-but-opinionated view, they are effectively
scope-bound, dynamic type aliases. Declaring func "foo<T>" is like
declaring “var i”, but its forgiven since coming up with a good, concise
name for such a type alias can be hard.

The standard library seems inconsistent about this as well:

func == <A : Equatable, B : Equatable>(_: (A, B), rhs: (A, B))

vs.

func == <Key : Equatable, Value : Equatable>(_: [Key : Value], rhs: [Key :
Value])

The argument I bring up is that naming of the generic parameters may wind
up affecting whether the code is clearer having type constraints and the
where clause within the brackets or trailing the function. It is important
to take this into account and compare both apples to apples and oranges to
oranges when evaluating syntax.

(or, change the API guide and standard library to discourage either apples
or oranges)

-DW

I'll keep this short. IMO:

* Dictionaries have semantics, so Key/Value makes sense.
* Truly "generic" equatable values do not, so A and B are simple stand-ins.
* Always prefer named tokens when there are actual semantics (Element,
Wrapped, etc).

This may or may not be appropriate for inclusion in the API guidelines.

-- E

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

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

This is one of those where I keep hoping someone will pick it up and get the proposal out there.

-- E

···

On Apr 29, 2016, at 8:57 AM, David Hart via swift-evolution <swift-evolution@swift.org> wrote:

What’s up with this great idea? Can’t see a proposal on swift-evolution anywhere.

On 08 Apr 2016, at 08:15, Chris Lattner via swift-evolution <swift-evolution@swift.org> wrote:

On Apr 6, 2016, at 11:30 AM, Developer via swift-evolution <swift-evolution@swift.org> wrote:

If you've ever gotten to the point where you have a sufficiently generic interface to a thing and you need to constrain it, possibly in an extension, maybe for a generic free function or operator, you know what a pain the syntax can be for these kinds of operations. For example, the Swift book implements this example to motivate where clauses

func anyCommonElements <T: SequenceType, U: SequenceType where T.Generator.Element: Equatable, T.Generator.Element == U.Generator.Element> (lhs: T, _ rhs: U) -> Bool

This is noisy and uncomfortable to my eyes, and almost impossible to align correctly. Per a short discussion on Twitter with Joe Groff and Erica Sadun, I'd like so see what the community feels about moving the where clause out of the angle brackets. So that example becomes

func anyCommonElements <T: SequenceType, U: SequenceType>
where T.Generator.Element: Equatable, T.Generator.Element == U.Generator.Element
(lhs: T, _ rhs: U) -> Bool

Or, if you're feeling ambitious, even

func anyCommonElements <T, U>
where T : SequenceType, U : SequenceType,
T.Generator.Element: Equatable, T.Generator.Element == U.Generator.Element
(lhs: T, _ rhs: U) -> Bool

Thoughts?

+1, long overdue. Please keep basic constraints (ones expressible without a ‘where’ clause, like simple conformances) inline though.

-Chris
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

These are fantastic proposals! With these in place, there would be precious little to envy Haskell.

For example, regarding “counter-proposal against making generic parameters public”, the current behaviour of Swift is essentially a bug, as reported by Noah Blake (https://bugs.swift.org/browse/SR-1065\).

Then there is:

(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... { /* ... */ }

Not only that this would make certain Standard Library signatures less embarrassing, but would enable entirely new programming patterns. For example, the size of a fixed-length sequence could be type guaranteed like so:

    let v = Vector<_2,_0,_1,_6>(repeatedValue: 42)
    v.count // 2016

… wehere types like _2 and _6 may be caseless enums:

    enum _2 : DecimalDigitType { }

milos

···

On 7 Apr 2016, at 09:44, Pyry Jahkola via swift-evolution <swift-evolution@swift.org> wrote:

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
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

+1 I almost fully agree with all your points. However I disagree with your first point:

What do you mean by: "I think it's more natural to restrict generic type parameters to the immediate local scope"

To me it feels like an "associated type" so it should be almost equally exposed in comparison to "associatedtype" of a protocol

Kind regards
- Maximilian

···

Am 07.04.2016 um 10:44 schrieb Pyry Jahkola via swift-evolution <swift-evolution@swift.org>:

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
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution