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

L. Mihalkovic laurent.mihalkovic at gmail.com
Sat Jun 25 12:09:23 CDT 2016



Regards
(From mobile)

> On Jun 25, 2016, at 6:34 PM, Thorsten Seitz via swift-evolution <swift-evolution at swift.org> wrote:
> 
> Sorry for the late reply — I had hoped to be able to think more deeply about various points, 
> but I’m going to delay that instead of delaying the reply even more :-)
> 
> 
>> Am 17.06.2016 um 19:04 schrieb Dave Abrahams <dabrahams at apple.com>:
>> 
>> 
>> on Thu Jun 16 2016, Thorsten Seitz <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.
> 
> Thanks! I’m very glad about that!
> 
> 
>> 
>>>>> (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.  
> 
> I never had assumed that this had been decided lightly ;-)
> And I have been favorably impressed by the rationales put forth so far by the Swift 
> team, so it would definitely be interesting to learn a bit more about the rationale
> being put into that decision and the advantages and disadvantages discussed back then.
> Is there something written down somewhere? 
> 

I think some applicable rational exist in type-system papers that came out of studying scala's. 

>> 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.
> 
> I still believe it would have advantages but I’ll concede that this discussion 
> will probably not help advancing Swift as this decision has been made.
> Still, it might be of interest to keep in mind for further design considerations.

Somehow the world of languages is small, and tracing inspiriation across is playing permutations on a limited set. 

> 
>> 
>>> 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
> 
> I meant artifical in the sense that a different design choice would have been possible.
> 
> 
>> “generic type” you just mean anything that encodes a static type
>> relationship, lots of things fall into that bucket.
> 
> Well, I think Java’s generics are not that advanced, so the bucket does not 
> have to be very big :-)

I am curious to see what language you have in mind when you are making a comparison?

> 
> 
>> 
>>>> 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.
> 
> Ok.
> 
> 
>> 
>>>>> 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.
>> 
>>> 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.
> 
> Thanks!
> 
> 
>> 
>>>>> 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.
> 
> Hmm, I’ll have to think more about that.
> 
> 
>> 
>>> 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 ;-)
> 
> If you happen to remember, I’d be interested in hearing about the problems you meant.
> 
> 
>> 
>>>>> 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.
> 
> Agreed, it is not part of most OOP *implementations* while being compatible with OOP.
> There have been lots of papers and research languages about typing problems like 
> binary methods, null pointers etc., though, so taking the mainstream OO languages
> as the yardstick for OOP is jumping a little bit too short IMO. 
> 
> 
>> 
>>>> 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 ;-)
> 
> Indeed :-)
> 
> 
>> 
>>> 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.
> 
> Sorry, the difficulty with Self I was thinking of only occurs when Self is in a covariant position
> which is not the case in Comparable, of course. Let’s take a modified example instead with Self 
> in a covariant position:
> 
> Swift:
> 
> protocol Minimizable {
>     func min(from other: Self) -> Self
> }
> 
> final class A : Minimizable { // has to be final
>  
>     let x: Int
>     
>     init(x: Int) {
>         self.x = x
>     }
>     
>     func min(from other: A) -> A {
>         return x < other.x ? self : other
>     }
> }
> 
> Ceylon:
> 
> interface Minimizable<Other> of Other given Other satisfies Minimizable<Other> {
>     shared formal Other min(Other other);
> }
> 
> class A() satisfies Minimizable<A> {
> 
>     Integer x = 0;
> 
>     shared actual default A min(A other) {
>         if (x < other.x) {
>             return this;
>         } else {
>             return other;
>         }
>     }
> }
> 
> In Ceylon class A does not have to be final and choosing the minimum of two values would be available for values from the whole subtree of types rooted in A (the `of` ensures that such a declaration cannot „cross“ into other subtrees) whereas `Self` enforces that there is no subtree below class A.
> 
> I have to admit that I am not well versed using `Self`, yet, so maybe I’m wrong here. In addition I am sure that `Self` allows designs
> that are not possible with Ceylon’s `of`.
> 
> The usage of Ceylon’s `of` for enumeration types is as follows (example taken from http://ceylon-lang.org/documentation/tour/types/):
> 
> abstract class Node() of Leaf | Branch {}
> 
> class Leaf(shared Object element) 
>         extends Node() {}
> 
> class Branch(shared Node left, shared Node right) 
>         extends Node() {}
> void printTree(Node node) {
>     switch (node)
>     case (is Leaf) {
>         print("Found a leaf: ``node.element``!");
>     }
>     case (is Branch) {
>         printTree(node.left);
>         printTree(node.right);
>     }
> }
> 
> 
> 
>> 
>>> 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, you can't
>> make an efficient array with value semantics in Eiffel.  That, IMO,
>> cannot be considered a language with first-class value types.
> 
> I haven’t had time yet to really evaluate that paper, but if you are right, then I agree
> with you that Eiffel cannot be considered as having first-calss value types.
> 
> At least one of the deficiencies listed in the paper does not exist anymore AFAIU 
> (expanded types not having constructors), though, so maybe things actually do have 
> changed since then.
> 
> 
>> 
>>> 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.
> 
> That’s certainly a valid point.
> 
> Furthermore I do understand (and fully support) that being interoperable with Objective-C is an 
> important restriction on Swift’s design space and I think it is absolutely awesome how
> that has been achieved!
> 
> 
>> 
>>> 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 ;-)
> 
> Ah, ok, I took your statement of protocols being needed for strong value semantics
> to be of general validity, not confined to Swift :-) 
> Within Swift that is certainly true!
> 
> -Thorsten
> 
> 
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160625/f7dd96da/attachment.html>


More information about the swift-evolution mailing list