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

L Mihalkovic laurent.mihalkovic at gmail.com
Fri Jun 17 15:08:14 CDT 2016


> On Jun 17, 2016, at 7:04 PM, Dave Abrahams via swift-evolution <swift-evolution at swift.org> wrote:
> 
> 
> on Thu Jun 16 2016, Thorsten Seitz <tseitz42-AT-icloud.com <http://tseitz42-at-icloud.com/>> wrote:
> 
>>> Am 13.06.2016 um 04:04 schrieb Dave Abrahams <dabrahams at apple.com>:
>>> 
>>> 
>>> on Fri Jun 10 2016, Thorsten Seitz <tseitz42-AT-icloud.com> wrote:
>>> 
>>>>> Am 09.06.2016 um 19:50 schrieb Thorsten Seitz via swift-evolution <swift-evolution at swift.org>:
>>>>> 
>>>>> 
>>>>>> Am 09.06.2016 um 18:49 schrieb Dave Abrahams via swift-evolution <swift-evolution at swift.org>:
>>>> 
>>>>>> 
>>>>>> on Wed Jun 08 2016, Jordan Rose <swift-evolution at swift.org> wrote:
>>>>>> 
>>>>>>>> On Jun 8, 2016, at 13:16, Dave Abrahams via swift-evolution
>>>>>>>> <swift-evolution at swift.org> wrote:
>>>>>>>> 
>>>>>>>> 
>>>>>>>> on Wed Jun 08 2016, Thorsten Seitz
>>>>>>> 
>>>>>>>> <swift-evolution at swift.org
>>>>>>>> <mailto:swift-evolution at swift.org>>
>>>>>>>> wrote:
>>>>>>>> 
>>>>>>>>> Ah, thanks, I forgot!  I still consider this a bug, though (will have
>>>>>>>>> to read up again what the reasons are for that behavior).
>>>>>>>> 
>>>>>>>> Yes, but in the case of the issue we're discussing, the choices are:
>>>>>>>> 
>>>>>>>> 1. Omit from the existential's API any protocol requirements that depend
>>>>>>>> on Self or associated types, in which case it *can't* conform to
>>>>>>>> itself because it doesn't fulfill the requirements.
>>>>>>>> 
>>>>>>>> 2. Erase type relationships and trap at runtime when they don't line up.
>>>>>>>> 
>>>>>>>> Matthew has been arguing against #2, but you can't “fix the bug” without
>>>>>>>> it.
>>>>>>> 
>>>>>>> #1 has been my preference for a while as well, at least as a starting
>>>>>>> point.
>>>>>> 
>>>>>> I should point out that with the resyntaxing of existentials to
>>>>>> Any<Protocols...>, the idea that Collection's existential doesn't
>>>>>> conform to Collection becomes far less absurd than it was, so maybe this
>>>>>> is not so bad.
>>>>> 
>>>>> I think the problem is more that Any<Collection> does not conform to
>>>>> a specific value for a type parameter T: Collection
>>>>> 
>>>>> What I mean by this is that `Collection` denotes a type family, a
>>>>> generic parameter `T: Collection` denotes a specific (though
>>>>> unknown) member of that type family and `Any<Collection>` denotes
>>>>> the type family again, so there is really no point in writing
>>>>> Any<Collection> IMO.
>>>>> The type family cannot conform to T because T is just one fixed member of it.
>>>>> It conforms to itself, though, as I can write
>>>>> let c1: Any<Collection> = …
>>>>> let c2: Any<Collection> = c1
>>>>> 
>>>>> That’s why I think that we could just drop Any<Collection> and simply write Collection.
>>>> 
>>>> Let me expand that a bit:
>>>> 
>>>> Actually all this talk about existentials vs. generics or protocols
>>>> vs. classes has had me confused somewhat and I think there are still
>>>> some misconceptions present on this list sometimes, so I’ll try to
>>>> clear them up:
>>> 
>>> There are several objectively incorrect statements here, and several
>>> others with which I disagree.  I was hoping someone else would write
>>> this for me, but since the post has such a tone of authority I feel I
>>> must respond.
>> 
>> You are right, the tone of my post was not appropriate, for which I
>> want to apologize sincerely.
> 
> My fundamental disagreement is with the content, not the tone.
> 
>> I still believe my statements to be valid, though, and will respond to
>> your arguments inline. Please don't get me wrong, I'm not trying to
>> have an argument for the argument's sake. All I want is to contribute
>> maybe a tiny bit to make Swift even better than it already is, by
>> sharing ideas and thoughts not only from me but from the designs of
>> other perhaps more obscure programming languages which I happen to
>> have stumbled upon in the past (often with much delight).
> 
> And I want you to know, even though I disagree with what you've written,
> that I very much appreciate the contribution you're making.
> 
>>>> (1) misconception: protocols with associated types are somehow very
>>>> different from generics
>>>> 
>>>> I don’t think they are and I will explain why. The only difference is
>>>> the way the type parameters are bound: generics use explicit parameter
>>>> lists whereas protocols use inheritance. That has some advantages
>>>> (think long parameter lists of generics) and some disadvantages.
>>>> These ways are dual in a notation sense: generic types have to have
>>>> all parameters bound whereas protocols cannot bind any of them.
>>>> The „existential“ notation `Any<>` being discussed on this list is
>>>> nothing more than adding the ability to protocols to bind the
>>>> parameters to be used just like Java’s wildcards are adding the
>>>> opposite feature to generics, namely not having to bind all
>>>> parameters.
>>> 
>>> Protocols and generics fulfill completely different roles in Swift, and
>>> so, **especially in a language design context like the one we're in
>>> here**, must be thought of differently.  The former are an abstraction
>>> mechanism for APIs, and the latter a mechanism for generalizing
>>> implementations.  
>> 
>> That's not what I was talking about. Of course, protocols are a
>> mechanism for deriving types from each other whereas generics are a
>> way to parameterize types. My point was that Swift's other way to
>> parameterize types, namely by associated types, is very similar to
>> generics with wildcards when looking a the existentials of such
>> protocols. In addition I was talking about generics in general, not
>> just about generics in Swift which restricts them to implementations
>> and does not support wildcards.
> 
> I'm aware of these other systems.  One of the problems with the way
> you're writing about this is that we're speaking in the context of Swift
> and you're assuming a completely open design space, as though Swift's
> choice to sharply distinguish classes from protocols was not a conscious
> one... but it was.  Yes, Swift could have been designed differently, so
> that a single language construct, a kind of generic class, was stretched
> so it could express almost everything.  Personally, I don't believe that
> results in a better language.
> 
>> Other languages like Java offer generics for interfaces as well and
>> support wildcards (adding generic types parameters to protocols in
>> Swift is currently discussed on the mailing list as well).  FWIW my
>> arguments were not about whether we should have wildcards in Swift or
>> not, but simply to relate one parametrization feature (associated
>> types) to a more well known parametrization feature (generics with
>> wildcards) in order to understand them better.
>> 
>>> The only place you could argue that they intersect is
>>> in generic non-final classes, because a class fills the dual role of
>>> abstraction and implementation mechanism (and some might say that's a
>>> weakness).  But even accounting for generic classes, protocols with
>>> associated types are very different from generics.  Two utterly
>>> different types (an enum and a struct, for example) can conform to any
>>> given protocol P, but generic types always share a common basis
>>> implementation.  
>> 
>> The latter is not the case for generic interfaces in Java, for
>> example, so it is just an artificial restriction present in Swift.
> 
> It's not an artificial restriction, it's a design choice.  Sure, if by
> “generic type” you just mean anything that encodes a static type
> relationship, lots of things fall into that bucket.
> 
>>> There is no way to produce distinct instances of a generic type with
>>> all its type parameters bound,
>> 
>> That is true in Swift (except for generic classes) due to the
>> restriction just mentioned.
>> 
>>> but for any protocol P I can make infinitely many instances of P with
>>> P.AssociatedType == Int.
>> 
>> This likewise applies to generic interfaces and for generic types in
>> general if taking inheritance into account - just like you do here for
>> protocols.
>> 
>>> Back to the my original point: while protocols and generic types have
>>> some similarities, the idea that they are fundamentally the same thing
>>> (I know you didn't say *exactly* that, but I think it will be read that
>>> way) would be wrong and a very unproductive way to approach language
>>> evolution.
>> 
>> I said that protocols *with associated types* are much like generics
>> *with wildcards* and tried to show why.
> 
> If all you're trying to do is say that there's an analogy there, then we
> have no argument.
> 
>>>> Essentially `Any<Collection>` in Swift is just the same as
>>>> `Collection<?>` in Java (assuming for comparability’s sake that
>>>> Swift’s Collection had no additional associated types; otherwise I
>>>> would just have to introduce a Collection<Element, Index> in Java).
>>> 
>>> I don't see how you can use an example that requires *assuming away*
>>> assoociated types to justify an argument that protocols *with associated
>>> types* are the same as generics.
>> 
>> Note, that I said *additional* associated types, i.e. in addition to
>> .Element, even giving an example how the Java interface had to be
>> extended by a type parameter `Index` if this assumption was not
>> applied (still simplifying because Generator would have been more
>> correct which would have to be added as type parameter in addition to
>> `Index`).
>> 
>> So, in essence the comparison is between the following (I'm using Foo
>> now instead of Collection to avoid the differences mentioned. Note
>> that this has no impact on the argument at all):
>> 
>> protocol Foo {
>>    associatedtype T
>>    ...
>> }
>> 
>> interface Foo<T> {
>>    ...
>> }
> 
> Yes, those correspond.

would be difficult to say otherwise ;)  considering:

"In Swift, I suggest that we use the term protocol for this feature, because I expect the end result to be similar enough to Objective-C protocols that our users will benefit, and (more importantly) different enough from Java/C# interfaces and C++ abstract base classes that those terms will be harmful. The term trait comes with the wrong connotation for C++ programmers, and none of our users know Scala."


> 
>> My argument is that existentials of protocols with associated types
>> are just like generic types with wildcards, i.e. `Any<Foo>` in Swift
>> is just the same as `Foo<?>` in Java.
>> Likewise `Any<Foo where .T: Number>` is just the same as `Foo<?
>> extends Number>` in Java. For me that was an insight I wanted to
>> share.
> 
> It's a good one.
> 
>>>> And just like Collection<?> does not conform to a type parameter `T
>>>> extends Collection<?>` because Collection<?> is the type `forall
>>>> E. Collection<E>` whereas `T extends Collection<?>` is the type
>>>> `T. Collection<T>` for a given T.
>>>> 
>>>> In essence protocols with associated types are like generics with
>>>> wildcards.
>>> 
>>> It is true that generics with wildcards in Java *are* (not just “like”)
>>> existential types but I don't agree with the statement above.  Because
>>> Java tries to create an “everything is a class” world, generic classes
>>> with bound type parameters end up playing the role of existential type.
>>> But protocols in Swift are not, fundamentally, just existential types,
>>> and the resyntaxing of ProtocolName to Any<ProtocolName> for use in type
>>> context is a huge leap forward in making that distinction clear... when
>>> that's done (unless we leave Array<ProtocolName> around as a synonym for
>>> Array<Any<ProtocolName>>—I really hope we won't!)  protocols indeed
>>> *won't* be types at all, existential or otherwise.
>> 
>> I fully agree that protocols are not types, their existentials
>> are. But I haven't seen yet what we really *gain* from making that
>> distinction explicit (except an ugly type syntax :-).
> 
> For me, it helps distinguish static from dynamic polymorphism.
> 
>> And like I already wrote in this or another thread we would have to
>> apply the same logic to non-final classes, which are existentials,
>> too.
>>> 
>>>> Coming back to the questions whether (a) allowing existentials to be
>>>> used as types is useful
>>> 
>>> That's the only use existentials have.  They *are* types.  Of course
>>> they're useful, and I don't think anyone was arguing otherwise.
>> 
>> I'm pretty sure that there was a discussion about whether being able
>> to write something like Any<Collection> is useful. My wording was
>> certainly imprecise, though, and didn't make sense as written. I
>> should have said something like "whether adding the ability to use
>> existential types of protocols with unbound associated types is
>> useful".
>> 
>>> 
>>>> and (b) whether sacrificing type safety would somehow be necessary for
>>>> that, I think we can safely answer (a) yes, it *is* useful to be able
>>>> to use existentials like Any<Collection> as types, because wildcards
>>>> are quite often needed and very useful in Java (they haven’t been
>>>> added without a reason) (b) no, sacrificing type safety does not make
>>>> sense, as the experience with Java’s wildcards shows that this is not
>>>> needed.
>>> 
>>> I would call this “interesting information,” but hardly conclusive.
>>> Java's generics are almost exactly the same thing as Objective-C
>>> lightweight generics, which are less capable and less expressive in
>>> many ways than Swift's generics.  
>> 
>> I agree that Java does not have something like `Self` or associated
>> types (which are really useful for not having to bind all type
>> parameters explicitly, especially when binding type parameters to
>> other generics which makes for long type parameter lists in Java where
>> I have to repeat everything over and over again), but do you mean
>> something else here?
>> Especially in the context of sacrificing type safety?
> 
> I do, but it will take some research for me to recover my memory of
> where the holes are.  It has been years since I thought about Java
> generics.  It's also possible that I'm wrong ;-)
> 
>>>> Especially if something like path dependent types is used like
>>>> proposed and some notation to open an existential’s type is added,
>>>> which is both something that Java does not have.
>>>> 
>>>> (2) misconception: POP is different from OOP
>>>> 
>>>> It is not. Protocols are just interfaces using subtyping like OOP has
>>>> always done. They just use associated types instead of explicit type
>>>> parameters for generics (see above).
>>> 
>>> They are not the same thing at all (see above ;->).  To add to the list
>>> above, protocols can express fundamental relationships—like Self
>>> requirements—that OOP simply can't handle.
>> 
>> Eiffel has something like Self, it is called anchoring and allows
>> binding the type of a variable to that of another one or self (which
>> is called `Current` in Eiffel). And Eiffel does model everything with
>> classes which may be abstract and allow for real multiple inheritance
>> with abilities to resolve all conflicts including those concerning
>> state (which is what other languages introduce interfaces for to avoid
>> conflicts concerning state while still failing to solve *semantic*
>> conflicts with the same diamond pattern).
>> No protocols or interfaces needed. Why do you say this is not OOP? The
>> book which describes Eiffel is called "Object-Oriented Software
>> Construction" (and is now about 20 years old).
> 
> It's not *incompatible* with OOP, but it is not part of the essence of
> OOP either.  If you survey object-oriented languages, what you find in
> common is inheritance-based dynamic polymorphism and reference
> semantics.  Those are the defining characteristics of OOP, and taking an
> object-oriented approach to a given problem means reaching for those
> features.
> 
>>> There's a reason Java can't
>>> express Comparable without losing static type-safety.  
>> 
>> You are certainly right that Java is not the best language out there
>> especially when talking about type systems (I often enough rant about
>> it :-) but I'm not sure what you mean here. Java's Comparable<T> seems
>> quite typesafe to me. Or do you mean that one could write `class A
>> implements Comparable<B>` by mistake? That's certainly a weak point
>> but doesn't compromise type safety, does it?
> 
> Java has cleverly avoided compromising type safety here by failing to
> express the constraint that comparable conformance means a type can be
> compared to itself ;-)

somehow considering how dynamic java is, it would have been short-sighted to express the constraint in the type itself rather than as it is, in generic methods where it can be guaranteed.
public static <T extends Comparable <http://docs.oracle.com/javase/6/docs/api/java/lang/Comparable.html><? super T>> void sort(List <http://docs.oracle.com/javase/6/docs/api/java/util/List.html><T> list)



> 
>> Ceylon has an elegant solution for that without using Self types:
>> 
>> interface Comparable<in Other> of Other given Other satisfies Comparable<Other> {...}
>> 
>> Note the variance annotation (which Swift currently has not) and the
>> `of` which ensures that the only subtype of Comparable<T> is T. This
>> is a nice feature that I haven't seen often in programming languages
>> (only Cecil comes to mind IIRC) and which is used for enumerations as
>> well in Ceylon. In Swift I cannot do this but can use Self which
>> solves this problem differently, albeit with some drawbacks compared
>> to Ceylon's solution (having to redefine the compare method in all
>> subtypes, 
> 
> That sounds interesting but is a bit vague.  A concise example of how
> this plays out in Swift and in Ceylon would be instructive here.
> 
>> which has lead to lengthy discussion threads about Self, StaticSelf,
>> #Self etc.).
>> 
>>> Finally, in a
>>> language with first-class value types, taking a protocol-oriented
>>> approach to abstraction leads to *fundamentally* different designs from
>>> what you get using OOP.
>> 
>> Eiffel has expanded types which are value types with copy semantics
>> quite like structs in Swift. These expanded types are pretty much
>> integrated into Eiffel's class-only type system. Just define a class
>> as `expanded` and you are done. 
> 
> Unless this part of the language has changed since 1996, or unless I've
> misread https://www.cs.kent.ac.uk/pubs/1996/798/content.pdf <https://www.cs.kent.ac.uk/pubs/1996/798/content.pdf>, you can't
> make an efficient array with value semantics in Eiffel.  That, IMO,
> cannot be considered a language with first-class value types.

java/jvm is about to throw an interesting wrench into the status-quo. Between the different forms of non-ref based arrays that can be optimized down to the JVM (azul is ahead of everyone in that arena) and value types, a lot of preconceived notions will have to go away (which I fully expect they won’t anytime soon considering how we like to hold on to things).


> 
>> Eiffel seems to have no need to introduce interfaces or protocols to
>> the language to support value types.  
> 
> No, of course not.  By saying that everything from abstract interfaces
> to static constraints and even value types is to be expressed a kind of
> possibly-generic class, you can eliminate distinctions in the language
> that IMO help to clarify design intent.  This is a language design
> choice one could make, but not one I'd want to.  In LISP, everything is
> an S-expression.  That has certain upsides, but for me it fails the
> expressivity test.
> 
>> You can even derive from expanded classes which is currently not
>> possible in Swift but has already been discussed several times on this
>> mailing list.  Polymorphic usage is only possible for non expanded
>> super types, which means as far as I understood that a reference is
>> used in that case. Variables with an expanded type do not use refences
>> and therefore may not be used polymorphically in Eiffel.  This should
>> be similar in Swift, at least as far as I did understand it. The
>> question whether variables with a value type can be used
>> polymorphically currently does not arise in Swift as structs cannot
>> inherit from each other (yet?).
>> 
>>> 
>>>> The more important distinction of Swift is emphasizing value types and
>>>> making mutation safely available by enforcing copy semantics for value
>>>> types.  
>>> 
>>> We don't, in fact, enforce copy semantics for value types.  That's
>>> something I'd like to change.  But regardless, value types would be a
>>> *lot* less useful if they couldn't conform to protocols, and so they
>>> would be a lot less used.  Heck, before we got protocol extensions in
>>> Swift 2, there was basically *no way* to share implementation among
>>> value types.  So you can't take protocols out of the picture without
>>> making value types, and the argument for value semantics, far weaker.
>> 
>> Why? Like I said, Eiffel *has* value types without needing
>> protocols. They just have a unified mechanism built around classes.
> 
> Because I'm speaking about Swift, not some other world where Protocol ==
> Generic Class ;-)
> 
> -- 
> -Dave
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org <mailto:swift-evolution at swift.org>
> https://lists.swift.org/mailman/listinfo/swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160617/fb306e11/attachment.html>


More information about the swift-evolution mailing list