[Review] SE-0089: Replace protocol<P1, P2> syntax with Any<P1, P2>


(Chris Lattner) #1

Hello Swift community,

The review of "SE-0089: Replace protocol<P1,P2> syntax with Any<P1,P2>" begins now and runs through May 30. The proposal is available here:

  https://github.com/apple/swift-evolution/blob/master/proposals/0095-any-as-existential.md

Reviews are an important part of the Swift evolution process. All reviews should be sent to the swift-evolution mailing list at

  https://lists.swift.org/mailman/listinfo/swift-evolution

or, if you would like to keep your feedback private, directly to the review manager.

What goes into a review?

The goal of the review process is to improve the proposal under review through constructive criticism and contribute to the direction of Swift. When writing your review, here are some questions you might want to answer in your review:

  * What is your evaluation of the proposal?
  * Is the problem being addressed significant enough to warrant a change to Swift?
  * Does this proposal fit well with the feel and direction of Swift?
  * If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?
  * How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

More information about the Swift evolution process is available at

  https://github.com/apple/swift-evolution/blob/master/process.md

Thank you,

-Chris Lattner
Review Manager


Joining Old Orphaned Posts to the Main Threads
(Joe Groff) #2

'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


(Matthew Johnson) #3

   * What is your evaluation of the proposal?

+1. This proposal pages the way for generalized existentials by getting the breaking change out of the way.

I encourage careful consideration of issues surrounding whether this proposal should retain the lowercase keyword convention with 'any', matching the current lowercase 'protocol' or not.

The argument for adopting uppercase is primarily that existentials are often used as types and type names are uppercase by convention.

My opinion tends towards retaining lowercase primarily because uppercase 'Any' looks like generic type but behaves differently. While there is some overlap, it cannot be used in all of the syntactic forms that generic types can, and it can be used in syntactic locations where generic types cannot. I believe this rules out the "fits into a syntactic slot" exemption for lowercasing keywords. Such an exemption should only apply when keywords are used in a way that is indistinguishable from user-defined constructs (such as dynamicType).

Another argument for upper case is the idea that we will directly use existentials in place of current type erased wrappers. I would prefer to see us establish the practice of declaring typealiases for commonly used existentials (which will quickly become verbose if used directly everywhere in out code).

The example given is 'Any<Sequence where .Element == String>'. I would prefer to see:

typealias AnySequence<T> = Any<Sequence where .Element == T>

This allows us to continue saying 'AnySequence<String>' in our code. This is a relatively simple constrained existential and yet I believe it reads much better than the raw existential. The difference will be quite significant with more complex existentials.

   * Is the problem being addressed significant enough to warrant a change to Swift?

Yes. 'protocol<>' doesn't feel like the right syntax for generalized existentials.

   * Does this proposal fit well with the feel and direction of Swift?

Yes.

   * If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

I don't believe I have.

   * How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

In depth study and participation in the discussion.

···

More information about the Swift evolution process is available at

   https://github.com/apple/swift-evolution/blob/master/process.md

Thank you,

-Chris Lattner
Review Manager

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


(Lily Ballard) #4

  * What is your evaluation of the proposal?

+1. protocol<...> feels like a weird corner of the language that not a lot of people know about. Also, changing to Any<...> opens the door to allowing classes in the list instead of just protocols (protocol<UIViewController, MyProto> doesn't make much sense, but Any<UIViewController, MyProto> is fine).

I prefer to spell it as Any instead of any. I think the biggest reason is because Any, without brackets, is used as an existential type, and it would be weird to have a lowercase identifier used as a type. Arguably we could introduce any<...> and make Any just a typealias for any<>, but that seems potentially confusing, so I'd rather just have Any and Any<...>.

Joe Groff's infix notation proposal has some merit, and I'd like to mention one other point in favor: Any<P,Q> and Any<Q,P> are equivalent types, but it's not immediately obvious that they should be, since all other uses of type parameters care about type ordering. Infix notation gets rid of the assumption of ordering (since infix operators like + are usually commutative). But it is a more dramatic change, and we can always consider switching to that later if we want (I imagine that usage of protocol<...> is fairly rare so not many people will be impacted by this change), and it does have the drawbacks that Joe mentioned which are non-trivial.

  * Is the problem being addressed significant enough to warrant a change to Swift?

Probably, yeah. It removes a rarely-used construct in favor of one that people are probably more familiar with, and as I mentioned before it opens the door to allowing Any<SomeClass,SomeProto> (which is an Obj-C feature that Swift currently has no equivalent for).

  * Does this proposal fit well with the feel and direction of Swift?

Yes.

  * If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

The only language that comes to mind where I'm familiar with the equivalent functionality is Rust, and as Joe Groff already said, Rust uses an infix operator for this.

  * How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

A quick reading of the proposal and reading the review thread to date.

-Kevin Ballard

···

On Tue, May 24, 2016, at 11:06 AM, Chris Lattner wrote:


(Brent Royal-Gordon) #5

  * What is your evaluation of the proposal?

I am in favor. This is a necessary step towards many future features: class-plus-protocol types, the replacement/reimplementation of AnyObject with Any<class>, existentials with associated types, etc.

One reason to prefer `Any` over `any` which is not listed in the proposal is confusion with the unparameterized `Any` type. Having an uppercase `Any` and a lowercase `any<…>` is going to lead to a lot of confusion; people aren't going to remember whether they need the capitalized form or the lowercase one for any particular use. I don't think we can have `any<...>` unless we're also willing to have an unparameterized `any`, and I think `any` is 100% wrong, because it is absolutely a type but is lowercase.

Since we are trying to cram as many breaking changes as possible into Swift 3, I also think we should consider now, or soon, whether or not we want to draw a strong syntactic line between protocols-as-existentials and protocols-as-constraints by requiring the use of `Any<…>` on all existentials and forbidding its use in constraints. That would mean, for instance, that code like this:

  let printable: CustomStringConvertible = foo

Would now be written:

  let printable: Any<CustomStringConvertible> = foo

And also that code like:

  func foo<X: Any<Y, Z>>(x: X)

Would probably have to be written something like:

  func foo<X: Y>(x: X) where X: Z

However, I believe this would have a significant advantage: it would clarify the distinction between an existential and a constraint. It would more clearly mark where you are taking on the abstraction overhead of an existential. It would also improve the non-existential type situation: in the short term, it would make it clearer where uses of associated type protocols like `Comparable` would not be permitted; in the long term, once we have type-erased existentials for those protocols, it would make it clearer when type erasure was in effect.

  * Is the problem being addressed significant enough to warrant a change to Swift?

Yes. `protocol<>` is an ugly and unloved corner of the language; very few people know about it, remember it, or use it. The renaming improves this situation.

  * Does this proposal fit well with the feel and direction of Swift?

Given the way it enables many future features, yes.

  * If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

The only language I've used with similar features is Objective-C. There, too, the `<>` is overloaded, now that lightweight generics are part of the language.

  * How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

I'd like to think I have a fairly deep understanding of this feature, having participated heavily in the discussions about it.

···

--
Brent Royal-Gordon
Architechies


(Dmitri Gribenko) #6

I like the direction about creating a unified syntax for current
implementation of existentials and future generalized existentials. I
am concerned about the chosen syntax though, I don't think it reads
right. I read Any<X, Y> as a union type.

var x1: Any // OK, 'x1' can have any dynamic type.
var x2: Any<ErrorProtocol> // OK, 'x2' is any value that conforms to
ErrorProtocol.

var x3: Any<Hashable, Comparable> // 'x3' is any of the following
types. Either a Hashable, or a Comparable.

Dmitri

···

--
main(i,j){for(i=2;;i++){for(j=2;j<i;j++){if(!(i%j)){j=0;break;}}if
(j){printf("%d\n",i);}}} /*Dmitri Gribenko <gribozavr@gmail.com>*/


(Jordan Rose) #7

[Proposal: https://github.com/apple/swift-evolution/blob/master/proposals/0095-any-as-existential.md ]

I’m late again to this one, but the discussion’s certainly still going. I’m pretty strongly against the bare ‘A & B’ syntax, for a few reasons:

- Swift doesn’t use ‘&’ for anything except bitwise AND. In particular, it didn’t get picked for set intersection.

- In theory people are allowed to overload ‘&’ to operate on types. That doesn’t make sense for all types, but you could probably come up with a particular type hierarchy or use of generics where it does make sense.

- I don’t like types with spaces in them when the spaces aren’t bracketed in some way. This is entirely an aesthetic objection, of the form “I don’t naturally read that as a type, or even as a single unit, when looking at code.” This isn’t a great objection because someone could have said it about `[Int]` or `Int?` as well. (It would be a more realistic objection if some of our brackets weren’t “<" and “>”.)

And I still support the ‘Any’ syntax, despite the “order doesn’t matter” problem:

- It’s consistent with the existing unconstrained ‘Any’ and with the current dedicated wrapper types. If we didn’t think it was a good name for those, we wouldn’t use it there either.

- It’s easily learnable. It’s rare enough that someone wouldn’t be exposed to it right away, like Optional, Array, and Dictionary sugar, but when they do see it it’ll be obvious that it’s a type, and probably obvious that it’s special because of the “Any”.

- It’s a type; types always start with uppercase letters. (This has been said plenty by others.)

None of these are particularly strong arguments, I know, and I don’t have a good alternate suggestion. But I did want to go on record as being in favor of ‘Any’ and against a binary operator, even though that seems to put me in the minority.

Jordan

···

On May 24, 2016, at 11:06, Chris Lattner <clattner@apple.com> wrote:

Hello Swift community,

The review of "SE-0089: Replace protocol<P1,P2> syntax with Any<P1,P2>" begins now and runs through May 30. The proposal is available here:

  https://github.com/apple/swift-evolution/blob/master/proposals/0095-any-as-existential.md

Reviews are an important part of the Swift evolution process. All reviews should be sent to the swift-evolution mailing list at

  https://lists.swift.org/mailman/listinfo/swift-evolution

or, if you would like to keep your feedback private, directly to the review manager.

What goes into a review?

The goal of the review process is to improve the proposal under review through constructive criticism and contribute to the direction of Swift. When writing your review, here are some questions you might want to answer in your review:

  * What is your evaluation of the proposal?
  * Is the problem being addressed significant enough to warrant a change to Swift?
  * Does this proposal fit well with the feel and direction of Swift?
  * If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?
  * How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

More information about the Swift evolution process is available at

  https://github.com/apple/swift-evolution/blob/master/process.md

Thank you,

-Chris Lattner
Review Manager

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


(Austin Zheng) #8

I actually consider the angle brackets to be easier to read, because it's
visually easy to pair the starting "<" with the ending ">" and mentally
delineate the entire extent of the declaration. (I suspect I may be alone
in this, though :).

If we can come up with reasonable strawman syntax for adding constraints,
we should probably throw this idea into the mix. I meant to ask you
earlier, but forgot. I would be happy to submit a PR to add it to the
alternatives section, though, so it can be formally reviewed, discussed,
and considered.

Austin

···

On Tue, May 24, 2016 at 12:07 PM, Joe Groff via swift-evolution < swift-evolution@swift.org> wrote:

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


(Tyler Cloutier) #9

'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)

Perhaps just a comma separated list like a type-inheritance-clause, using parenthesis for disambiguation?

var x: P, Q
func foo<(T: P, Q), U>

or

func foo<T: (P, Q), U>

···

On May 24, 2016, at 12:07 PM, Joe Groff via swift-evolution <swift-evolution@swift.org> wrote:

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


(Matthew Johnson) #10

'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:

Using infix operator syntax was discussed. IIRC, the conclusion of the discussion is that it would be an alternative shorthand for the more verbose Any<> syntax (which would be necessary when you have constraints, but in that case you probably want to use a typealias anyway). The shorthand was deemed likely impractical for Swift 3 and the change from `protocol` to `Any` a good idea to get the breaking change out of the way. How do you feel about that 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> { … }

This is cool. If it is deemed a likely direction it is a good case for using the uppercase `Any`. The preference I stated for lowercase is based on trying to apply our casing rules constantly and library types use uppercase.

···

On May 24, 2016, at 2:07 PM, Joe Groff via swift-evolution <swift-evolution@swift.org> wrote:

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


(Brent Royal-Gordon) #11

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`.

Given our reluctance to even overload operators for use with Set, I'd personally find this a bit incongruous.

···

--
Brent Royal-Gordon
Architechies


(Brent Royal-Gordon) #12

- 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> { ... }

I wanted to reply to this, because this is a beautiful dream, but if we want to treat `Any` like some kind of magical variadic generic type, that has some interesting implications for its design right now.

1) In a normal generic type, when you leave out the generic parameters, Swift infers them. (Think of casting an array literal to `Array`, which pins down the collection type but not the elements.) If `Any` were handled similarly, that would mean that `foo as Any` would not necessarily have an empty set of constraints, but would rather have an *inferred* set of constraints. If you wanted to force it to have no constraints, you would need to write `Any<>`.

2) We would not normally expect to be able to cast between a normal generic type and the instance contained in it. Instead, we would expect to use initializers, just as we're now planning to require for Objective-C bridging:

  let myExistentialInteger = Any<Integer>(myInt)
  let myInt2 = Int(any: myExistentialInteger)!

Note that, if we were passing to an API which specifically expected an `Any<Integer>`, we could just write `Any(myInt)` and type inference would figure out the rest.

···

--
Brent Royal-Gordon
Architechies


(Austin Zheng) #13

I am not going to comment on the proposal (conflict of interest etc). I do want to speak up in support of Brent's points, though.

  * What is your evaluation of the proposal?

I am in favor. This is a necessary step towards many future features: class-plus-protocol types, the replacement/reimplementation of AnyObject with Any<class>, existentials with associated types, etc.

One reason to prefer `Any` over `any` which is not listed in the proposal is confusion with the unparameterized `Any` type. Having an uppercase `Any` and a lowercase `any<…>` is going to lead to a lot of confusion; people aren't going to remember whether they need the capitalized form or the lowercase one for any particular use. I don't think we can have `any<...>` unless we're also willing to have an unparameterized `any`, and I think `any` is 100% wrong, because it is absolutely a type but is lowercase.

Since we are trying to cram as many breaking changes as possible into Swift 3, I also think we should consider now, or soon, whether or not we want to draw a strong syntactic line between protocols-as-existentials and protocols-as-constraints by requiring the use of `Any<…>` on all existentials and forbidding its use in constraints. That would mean, for instance, that code like this:

  let printable: CustomStringConvertible = foo

Would now be written:

  let printable: Any<CustomStringConvertible> = foo

I'm sure this will be controversial, but I like the idea of marking all existential types using Any-syntax. It makes the distinction between concrete and existential types in code completely clear to the reader. Given that there are some subtle differences in how concrete and existential types can be used (for example, used as the types of values passed to generic functions), I think this is definitely worth considering.

There are an additional five characters (including two angle brackets to type), but I suspect bare existentials aren't used quite as often as concrete types are, so the aesthetic cost might not be too onerous.

And also that code like:

  func foo<X: Any<Y, Z>>(x: X)

Would probably have to be written something like:

  func foo<X: Y>(x: X) where X: Z

I also personally prefer this convention. The 'Any<Y, Z>' construct in the constraint above does not really have anything to do with existential types as they exist everywhere else in Swift; it simply means that whatever concrete type can be used to instantiate the function must conform to two different protocols.

It's true that any concrete type that could be used to satisfy X in foo() could also be used to fill an Any<Y, Z> existential, but this correspondence isn't interesting to the user. The ways a user can use an Any<Y, Z> existential differ from the ways a sort-of-universal type like X in foo() can be used:

// firstElement's type is known exactly at compile time, because X must be 'filled in'
// by a concrete type, and that concrete type's associated types are also known.
foo<X : Collection>(x: X) {
  let firstElement : X.Iterator.Element = x.first!
}

// firstElement's type is not ever known without some sort of dynamic downcasting,
// because the contract that defines the existential can guarantee nothing about its type.
let foo : <X : Collection> = ...
let firstElement : Any = x.first!

···

On May 25, 2016, at 12:34 AM, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> wrote:

However, I believe this would have a significant advantage: it would clarify the distinction between an existential and a constraint. It would more clearly mark where you are taking on the abstraction overhead of an existential. It would also improve the non-existential type situation: in the short term, it would make it clearer where uses of associated type protocols like `Comparable` would not be permitted; in the long term, once we have type-erased existentials for those protocols, it would make it clearer when type erasure was in effect.

  * Is the problem being addressed significant enough to warrant a change to Swift?

Yes. `protocol<>` is an ugly and unloved corner of the language; very few people know about it, remember it, or use it. The renaming improves this situation.

  * Does this proposal fit well with the feel and direction of Swift?

Given the way it enables many future features, yes.

  * If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

The only language I've used with similar features is Objective-C. There, too, the `<>` is overloaded, now that lightweight generics are part of the language.

  * How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

I'd like to think I have a fairly deep understanding of this feature, having participated heavily in the discussions about it.

--
Brent Royal-Gordon
Architechies

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


(Brent Royal-Gordon) #14

However, I believe this would have a significant advantage: it would clarify the distinction between an existential and a constraint. It would more clearly mark where you are taking on the abstraction overhead of an existential. It would also improve the non-existential type situation: in the short term, it would make it clearer where uses of associated type protocols like `Comparable` would not be permitted; in the long term, once we have type-erased existentials for those protocols, it would make it clearer when type erasure was in effect.

Sorry to self-reply, but I had some additional thoughts on this.

Swift's type system has a logical hierarchy of types. A type's supertypes include any protocols it conforms to, and then the protocols they conform to, ultimately rooted at Any. Additionally, a class's supertypes include its superclass, and then its superclass, and so on up to a root class, and then AnyObject, and finally Any once more. And of course, Any<…> lets you build composite types out of several protocols which fit into this framework: Any<Foo, Bar> is a subtype of both Foo and Bar and a supertype of any type which conforms to both Foo and Bar.

At an implementation level, however, this is only partially true. A class can be transformed into its superclass, all the way up to AnyObject, without any change in memory representation. But once you enter the world of protocols, everything changes. To cast to a protocol, you must place the instance in an existential, a different instance with a different memory layout. This can have significant performance consequences; for example, it's the reason you can't use `as` to convert an array of instances to an array of some protocol they all belong to.

Requiring the use of `Any<…>` to mark any existential would help reinforce this distinction. It would give users a better understanding of what their code was actually doing and why they might not be able to do things like make certain seemingly sensible casts.

(Although technically, this would seem to imply that AnyObject's future equivalent should *not* require an Any<>. Hmm.)

···

--
Brent Royal-Gordon
Architechies


(Matthew Johnson) #15

I like the direction about creating a unified syntax for current
implementation of existentials and future generalized existentials. I
am concerned about the chosen syntax though, I don't think it reads
right. I read Any<X, Y> as a union type.

var x1: Any // OK, 'x1' can have any dynamic type.
var x2: Any<ErrorProtocol> // OK, 'x2' is any value that conforms to
ErrorProtocol.

var x3: Any<Hashable, Comparable> // 'x3' is any of the following
types. Either a Hashable, or a Comparable.

Any suggestions? (pun intended)

···

Sent from my iPad

On May 25, 2016, at 12:28 PM, Dmitri Gribenko via swift-evolution <swift-evolution@swift.org> wrote:

Dmitri

--
main(i,j){for(i=2;;i++){for(j=2;j<i;j++){if(!(i%j)){j=0;break;}}if
(j){printf("%d\n",i);}}} /*Dmitri Gribenko <gribozavr@gmail.com>*/
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Vladimir) #16

Support that Any<Some, Another> could be confusing and could be read as "any Some or any Another". Although as I understand there is no support for this, I probably think this is more clear:

var x3: Any<Hashable & Comparable>

and reads like a poem :wink: "any that is hashable AND comparable"

···

On 25.05.2016 20:28, Dmitri Gribenko via swift-evolution wrote:

var x3: Any<Hashable, Comparable> // 'x3' is any of the following
types. Either a Hashable, or a Comparable.


(Brent Royal-Gordon) #17

[Proposal: https://github.com/apple/swift-evolution/blob/master/proposals/0095-any-as-existential.md ]

I’m late again to this one, but the discussion’s certainly still going. I’m pretty strongly against the bare ‘A & B’ syntax, for a few reasons:

- Swift doesn’t use ‘&’ for anything except bitwise AND. In particular, it didn’t get picked for set intersection.

I very, *very* much agree with this point. Thus far, Swift has been very conservative about overloading. It seems bizarre that we would violate that policy for these type combinators when much closer matches have been passed over.

I also think that using `&` absolutely *begs* to have an `|` as well, and as much as the Ceylon people clamor for them, I don't think union types make sense in Swift. Unlike Ceylon, Swift uses an algebraic data type for Optional, doesn't use type-erased generics, and supports overloading. As far as I can tell, *none* of the reasons cited in the Ceylon language design FAQ <http://ceylon-lang.org/documentation/1.2/faq/language-design/> apply to Swift.

(P.S. That language design FAQ is a *fantastic* document.)

- In theory people are allowed to overload ‘&’ to operate on types. That doesn’t make sense for all types, but you could probably come up with a particular type hierarchy or use of generics where it does make sense.

- I don’t like types with spaces in them when the spaces aren’t bracketed in some way. This is entirely an aesthetic objection, of the form “I don’t naturally read that as a type, or even as a single unit, when looking at code.” This isn’t a great objection because someone could have said it about `[Int]` or `Int?` as well. (It would be a more realistic objection if some of our brackets weren’t “<" and “>”.)

To augment this: I think that if we're going to see a lot of little `where` clauses attached to types, you kind of have to have a bracketing syntax to put them in. And no, `(Collection where .Element == String)` doesn't count. `()` is about precedence, and this isn't a question of precedence; it's a question of making the type read as a single unit.

And I still support the ‘Any’ syntax, despite the “order doesn’t matter” problem:

- It’s consistent with the existing unconstrained ‘Any’ and with the current dedicated wrapper types. If we didn’t think it was a good name for those, we wouldn’t use it there either.

One of my concerns with the `&` syntax is that it leaves no good way to define `Any`. I don't like having a magic `&` operator *and* a magic `Any` keyword, neither of which derives from anything more general.

- It’s easily learnable. It’s rare enough that someone wouldn’t be exposed to it right away, like Optional, Array, and Dictionary sugar, but when they do see it it’ll be obvious that it’s a type, and probably obvious that it’s special because of the “Any”.

- It’s a type; types always start with uppercase letters. (This has been said plenty by others.)

None of these are particularly strong arguments, I know, and I don’t have a good alternate suggestion. But I did want to go on record as being in favor of ‘Any’ and against a binary operator, even though that seems to put me in the minority.

I suspect that at least part of this is simply greater enthusiasm. I know I checked out of this thread for a few days while the `&` guys ran rampant.

(P.S. A thought I had just now: If `Any` is our top type, then `All` would be a good name for our bottom type. And `All<Foo, Bar>` would be a good "All common supertypes of these types" operation. Both of these came up in the variadic generics thread, where we were talking about how to create a type representing any member of a tuple you were trying to splat in.)

···

--
Brent Royal-Gordon
Architechies


(Matthew Johnson) #18

I actually consider the angle brackets to be easier to read, because it's visually easy to pair the starting "<" with the ending ">" and mentally delineate the entire extent of the declaration. (I suspect I may be alone in this, though :).

If we can come up with reasonable strawman syntax for adding constraints, we should probably throw this idea into the mix. I meant to ask you earlier, but forgot. I would be happy to submit a PR to add it to the alternatives section, though, so it can be formally reviewed, discussed, and considered.

Or maybe you should put it in the future directions section? IMO, it is pretty reasonable to consider the operator syntax to be shorthand, similar to the shorthand for Optional, Array, and Dictionary.

···

On May 24, 2016, at 2:20 PM, Austin Zheng via swift-evolution <swift-evolution@swift.org> wrote:

Austin

On Tue, May 24, 2016 at 12:07 PM, Joe Groff via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
'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
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto: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


(Matthew Johnson) #19

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`.

Given our reluctance to even overload operators for use with Set, I'd personally find this a bit incongruous.

I don't recall a proposal around operators for Set. What did people want to use? The obvious candidates are way too hard to type on the keyboards we have today.

···

Sent from my iPad
On May 24, 2016, at 6:09 PM, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> wrote:

--
Brent Royal-Gordon
Architechies

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


(Austin Zheng) #20

Because I am bad at typing today, my code examples should read:

foo<X : Collection>(x: X) { ... }

and

let foo : <Collection> = ...

···

On May 25, 2016, at 1:01 AM, Austin Zheng <austinzheng@gmail.com> wrote:

I am not going to comment on the proposal (conflict of interest etc). I do want to speak up in support of Brent's points, though.

On May 25, 2016, at 12:34 AM, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> wrote:

  * What is your evaluation of the proposal?

I am in favor. This is a necessary step towards many future features: class-plus-protocol types, the replacement/reimplementation of AnyObject with Any<class>, existentials with associated types, etc.

One reason to prefer `Any` over `any` which is not listed in the proposal is confusion with the unparameterized `Any` type. Having an uppercase `Any` and a lowercase `any<…>` is going to lead to a lot of confusion; people aren't going to remember whether they need the capitalized form or the lowercase one for any particular use. I don't think we can have `any<...>` unless we're also willing to have an unparameterized `any`, and I think `any` is 100% wrong, because it is absolutely a type but is lowercase.

Since we are trying to cram as many breaking changes as possible into Swift 3, I also think we should consider now, or soon, whether or not we want to draw a strong syntactic line between protocols-as-existentials and protocols-as-constraints by requiring the use of `Any<…>` on all existentials and forbidding its use in constraints. That would mean, for instance, that code like this:

  let printable: CustomStringConvertible = foo

Would now be written:

  let printable: Any<CustomStringConvertible> = foo

I'm sure this will be controversial, but I like the idea of marking all existential types using Any-syntax. It makes the distinction between concrete and existential types in code completely clear to the reader. Given that there are some subtle differences in how concrete and existential types can be used (for example, used as the types of values passed to generic functions), I think this is definitely worth considering.

There are an additional five characters (including two angle brackets to type), but I suspect bare existentials aren't used quite as often as concrete types are, so the aesthetic cost might not be too onerous.

And also that code like:

  func foo<X: Any<Y, Z>>(x: X)

Would probably have to be written something like:

  func foo<X: Y>(x: X) where X: Z

I also personally prefer this convention. The 'Any<Y, Z>' construct in the constraint above does not really have anything to do with existential types as they exist everywhere else in Swift; it simply means that whatever concrete type can be used to instantiate the function must conform to two different protocols.

It's true that any concrete type that could be used to satisfy X in foo() could also be used to fill an Any<Y, Z> existential, but this correspondence isn't interesting to the user. The ways a user can use an Any<Y, Z> existential differ from the ways a sort-of-universal type like X in foo() can be used:

// firstElement's type is known exactly at compile time, because X must be 'filled in'
// by a concrete type, and that concrete type's associated types are also known.
foo<X : Collection>(x: X) {
let firstElement : X.Iterator.Element = x.first!
}

// firstElement's type is not ever known without some sort of dynamic downcasting,
// because the contract that defines the existential can guarantee nothing about its type.
let foo : <X : Collection> = ...
let firstElement : Any = x.first!

However, I believe this would have a significant advantage: it would clarify the distinction between an existential and a constraint. It would more clearly mark where you are taking on the abstraction overhead of an existential. It would also improve the non-existential type situation: in the short term, it would make it clearer where uses of associated type protocols like `Comparable` would not be permitted; in the long term, once we have type-erased existentials for those protocols, it would make it clearer when type erasure was in effect.

  * Is the problem being addressed significant enough to warrant a change to Swift?

Yes. `protocol<>` is an ugly and unloved corner of the language; very few people know about it, remember it, or use it. The renaming improves this situation.

  * Does this proposal fit well with the feel and direction of Swift?

Given the way it enables many future features, yes.

  * If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

The only language I've used with similar features is Objective-C. There, too, the `<>` is overloaded, now that lightweight generics are part of the language.

  * How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

I'd like to think I have a fairly deep understanding of this feature, having participated heavily in the discussions about it.

--
Brent Royal-Gordon
Architechies

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