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.
This has been a point of confusion for me as well. I keep hearing that associated types are different from generic protocols, but this seems like a distinction without a difference.
Suppose Swift allowed generic protocols. How would a hypothetical Collection<Foo> be different in practice from the proposed existential Any<Collection where .Element == Foo>?
Yes, in the realm of type theory and compiler internals they might represented differently, sure. But in practice, in terms of what code can actually do? I know of only two differences:
1. A type can only conform to any given protocol with one set of type parameters. (Nothing can be both Collection<Foo> and Collection<Bar>.)
2. When a type conforms to Collection, it declares “associatedtype Foo” instead of “: Collection<Foo>”, and Foo can be inferred by the compiler in some circumstances. That’s handy, but it’s a syntactic difference.
Wasn't there something recently from chris about removing inference?
···
On Jun 16, 2016, at 5:36 PM, Paul Cantrell via swift-evolution <swift-evolution@swift.org> wrote:
On Jun 16, 2016, at 8:29 AM, Thorsten Seitz via swift-evolution <swift-evolution@swift.org> wrote:
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.
This has been a point of confusion for me as well. I keep hearing that associated types are different from generic protocols, but this seems like a distinction without a difference.
Suppose Swift allowed generic protocols. How would a hypothetical Collection<Foo> be different in practice from the proposed existential Any<Collection where .Element == Foo>?
Yes, in the realm of type theory and compiler internals they might represented differently, sure. But in practice, in terms of what code can actually do? I know of only two differences:
1. A type can only conform to any given protocol with one set of type parameters. (Nothing can be both Collection<Foo> and Collection<Bar>.)
2. When a type conforms to Collection, it declares “associatedtype Foo” instead of “: Collection<Foo>”, and Foo can be inferred by the compiler in some circumstances. That’s handy, but it’s a syntactic difference.
That syntactic difference is *very* handy IMO for the following reason: with generics I have to repeat all types over and over again which gets ugly when I have levels of nesting where type parameters are constrained by other generics, which requires adding their parameters to the parameter list. Essentially the nested parameters have to be fully flattened because each type parameter has to be explicitly specified.
I’ll try to show that with a simplified example:
// with associated types
protocol Edge {
associatedtype VertexType
var source: VertexType { get }
var target: VertexType { get }
}
protocol Graph {
associatedtype EdgeType : Edge
var vertices: [EdgeType.VertexType] { get }
var edges: [EdgeType] { get }
Note, how the parameter list for GraphIterator exploded, because I had to list each level of nested types down to the VertexType, whereas
in the associated types example the GraphIterator simply declares an associated type conforming to the topmost type of my nesting, the Graph.
Is there a deeper difference I’m missing?
Maybe Dave can chime in here?
-Thorsten
···
Am 16.06.2016 um 17:36 schrieb Paul Cantrell <cantrell@pobox.com>:
On Jun 16, 2016, at 8:29 AM, Thorsten Seitz via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@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 ;-)
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, 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).
···
On Jun 17, 2016, at 7:04 PM, Dave Abrahams via swift-evolution <swift-evolution@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@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@swift.org>:
Am 09.06.2016 um 18:49 schrieb Dave Abrahams via swift-evolution <swift-evolution@swift.org>:
on Wed Jun 08 2016, Jordan Rose <swift-evolution@swift.org> wrote:
On Jun 8, 2016, at 13:16, Dave Abrahams via swift-evolution >>>>>>>> <swift-evolution@swift.org> wrote:
on Wed Jun 08 2016, Thorsten Seitz >>>>>>> >>>>>>>> <swift-evolution@swift.org >>>>>>>> <mailto:swift-evolution@swift.org>> >>>>>>>> wrote:
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 ;-)
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 :-)
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?
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.
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 :-)
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:
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`.
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
···
Am 17.06.2016 um 19:04 schrieb Dave Abrahams <dabrahams@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@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@swift.org>:
Am 09.06.2016 um 18:49 schrieb Dave Abrahams via swift-evolution <swift-evolution@swift.org>:
on Wed Jun 08 2016, Jordan Rose <swift-evolution@swift.org> wrote:
On Jun 8, 2016, at 13:16, Dave Abrahams via swift-evolution >>>>>>>> <swift-evolution@swift.org> wrote:
on Wed Jun 08 2016, Thorsten Seitz >>>>>>> >>>>>>>> <swift-evolution@swift.org >>>>>>>> <mailto:swift-evolution@swift.org>> >>>>>>>> wrote:
The only magic would be that all type definitions (`protocol` etc.) which do not give a supertype they conform to, will implicitly conform to `Any`, i.e.
protocol Foo { … }
means
protocol Foo : Any { … }
Any is also the supertype of all structural types, and structural types cannot conform to protocols.
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:
(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.
As you know I like using `&` as type intersection operator. But you write "The syntax leave a void when it comes to expressing the so called Top type:“
Why? Just give the top type a name, e.g. `Any` and you are done.
Yes.. I just don't like magic. I think that all types should be expressable with a syntax and that you can then decide to alias one parsing case with a specific name. Not the other way around.
Well, I think that would be backwards, because we have a nominal type system. That means that the syntax for a type is just its name.
I realize we do not understand each other.
The words we use now to describe a behavior are just that, words. As is the concepts they describe are useless to the computer running the compiler.
So we need to map these concepts into a heuristic that a fast but dumb computer will be able to reason with. The code to do that will be either clean and organized, or it will look contrived and full of different paths that will be difficult to mesh together. Of all the possible ways in which swift can behave as a language, some will lead to the former, others to the latter code. I think this is not fate or something you find out after the decision was made and you struggle to carry it through. This is something that can partially be predicted. One of the tools for such prediction is to translate the concepts into a grammar that will show formalize the logic. The simpler the logic, the cleaner (not simple) the final code.
Saying that the syntax for a type is a name is of no use whatsoever for the compiler implementer. It is so universally true that it cannot help in any way whatsoever decide the shape of the swift grammar, much less the structure of the c++ code implementing it.
`&` is a type operator which creates a new type from its operands.
`where` clauses add constraints to types.
But it all starts with a type's name.
The only magic would be that all type definitions (`protocol` etc.) which do not give a supertype they conform to, will implicitly conform to `Any`, i.e.
I call magic the core notions of swift that cannot be expressed in swift but have to parachutted in by the compiler. If you read Chris' initial message you will see that he said as much, that adopting P&Q as syntax was leaving a gap that the compiler would have to magically fill.
protocol Foo { … }
means
protocol Foo : Any { … }
That’s less magic than creating a special syntax just to express the top type.
I will try again... Would it be easier to understand it if instead of magic I said arbitrary? You are creating a special case: you decide arbitrarily that the special series of characters 'A' 'n' 'y' with this precise casing and without any spaces is going to be adorned with a special meaning when used in specific situations. This is not something you deduced. My approach is exactly the opposite: _ does not describe the top type because I have a special affinity with these characters, it describes the top type because it is the only possible logical conclusion from having followed a set of rules attached to a syntax that describes ALL existentials. And because it is not particularly savory, I typealias it to something else. But this is not in the compiler, it us in the standard linrary... inside swift, not outside.
Read Chris' original announcement.. He describes P&Q as it would be adopted today as being just a CORNER CASE of a single general principal... a single grammar that can work today to describe P&Q as well as generalize existentials tomorrow. No special treatment, single parser, single rule. I don't like to write un-necessary IF statements in code.
Why should the top type have special *syntax*? It is just the type all other types conform to. No need to do something special here and therefore no need to invent an alternative syntax like `Any<>` or the alternative from your gist which is rather confusing IMO (and I don’t like the special case given to classes in the syntax).
The _ case is just a degenerate case of the syntax, showing that it is expressible inside, as opposoed to have to define a contextual keyword (read the SourceKit code). And then it is aliasable. Part of the problems in swift today to me is that some things are no doable in swift, so the compiler must contain that semantic. This here is just one example, but there are others. These are notions that the standard library has to defer to the compiler. Some of them have been tabled for 4.0 it seems. My point here was that it is not fate, and choosing the syntax carefully for existentials (whichever it is, i don't really care inthe end) would prevent having to add a magic keyword for to top type to the compiler and to SourceKit again (they are trying to remove them).
`Any` would *not* be a keyword. It is just a type name like `Collection` or `Int`. Like I said, the only magic would be in adding `: Any` to type definitions without a conforming clause.
I hope that by now you understand what magic I was talking about.
As for the current Any<...> proposal for generalizing existentials it is IMHO cluncky and magic. There is nothing stopping us mechanically from entering Any<UITableView, UIButton>. To me that is the holemark of bad design. In what I tested you just can't do it and the reason is clear. Of course when I
`Any<UITableView, UIButton>` is just an intersection type and would be written UITableView & UIButton. And, yes, that would be ok, because it would be just the empty set, i.e. the bottom type (which has no members). There is no magic involved, it is just the normal result of an intersection: each type is a set containing all instances of this type (instances conforming to it)
You can't be serious? You are saying that to you the ide should not be telling us we are writting an absurdity? And this one was obvious, but it gets a lot worse with more complex types.
. Intersecting two sets might result in an empty set. The type denoting the empty set is the bottom type which is the subtype of all types and might be called `Nothing` or `Never` or `Bottom`.
Ceylon makes very nice use of type intersections and type unions and the beautiful thing is that these type operations really just work like you would expect if you think of types as sets (which is the standard definition for a type AFAIK). No surprises and no magic there!
So it is *not* a sign of bad design, quite to the contrary! What did you test it with? Probably not Ceylon, because otherwise you would have seen that it just works.
Hey, who knows, ceylon may one day come to llvm...
say you can't I do not mean that your keyboard will zap u if you try. But the way to verify correctness is different... and in a world where xcode would be a good ide, then code completion would even show you why and actually truly assist (because the grammar i tested makes really makes it possible). I know that xcode is only as good as SourceKit lets it be.
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).
Likewise `Any<Collection where .Element: Number>` is just the same as `Collection<? extends Number>` in Java.
Java’s wildcards are a way to express use-site variance. The proposed `Any<>` does just the same.
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.
This picture is accurate today, but there are going to be more serious differences after 10 no date is currently geven for when it will come)
You mean Java 10?
Yes. After 10. Than is their only date hint.
In essence protocols with associated types are like generics with wildcards.
Yes, java has kept everything within its generics system rather than split parts out. Something people may not immediately think about with respect to the 2 generic systems is that when u call a func<T>capture(T t){} in java with a wildcard you are doing a compile time capture only (to avoid the dreaded unsafe casts), whereas it is really nice to do the same in swift and subsequently be able to access T.Type and see that it is not Any. The closest u ever get to that type at runtime in java is via generics introspection, but u still can't do everything ( like no new T() ). But today the bridging between existential types and generics is definitely a work in progress.
Coming back to the questions whether (a) allowing existentials to be used as 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)
IMO they made java 8 (referring to streams). And even though the syntax for co/contra variance is pretty heavy, it is the foundation for all modern java code. The any-fication of the generics is going to open new doors as some of it will translate into a partial reification in the jvm. It seems the decision for now is to not use the extra info in java to retain binary compatibility with all the erased code out there, this is something scala might use in areas where it won't mind loosing java compatibility.
(b) no, sacrificing type safety does not make sense, as the experience with Java’s wildcards shows that this is not needed. 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.
I hope typesafe opening inside the " if let " syntax gets added. I know that chris is against sugaring, but I played if an implementation of
... x is String?
If let! x {}
That runs as
if let x = x {}
something equally short could be done here.
(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). The more important distinction of Swift is emphasizing value types and making mutation safely available by enforcing copy semantics for value types.
Values are coming to the jvm, which will narrow this gap (like their view identity for value vs ref and the whole deep ==) . I also really like the cow approach of the swift runtime.
But protocols are not really different from interfaces in Java.
There is one big difference: default methods, but it seems swift will add that soon.
Swift already has default extension methods, doesn’t it?
Yes, and no. It has differently dispatched code that can be found to fill in the gap of the conformance req, yes. But Joe Grof (?) said that there are no reasons why these could not be added. Having true defaults could be one way to deal with optional comformance…
The dispatch issue only arises for extensions introducing a method that is not declared in a protocol. That is something you *cannot* do in Java. Java’s default methods are implementations for methods declared in interfaces. Swift’s extension methods providing defaults for methods declared in a protocol are dynamically dispatched and should work like Java’s default methods.
I think we should agree to disagree.
···
On Jun 11, 2016, at 2:05 PM, Thorsten Seitz <tseitz42@icloud.com> wrote:
Am 11.06.2016 um 12:38 schrieb L. Mihalkovic <laurent.mihalkovic@gmail.com>:
On Jun 11, 2016, at 11:30 AM, Thorsten Seitz <tseitz42@icloud.com> wrote:
Am 11.06.2016 um 08:00 schrieb L. Mihalkovic <laurent.mihalkovic@gmail.com>:
On Jun 10, 2016, at 9:35 PM, Thorsten Seitz via swift-evolution <swift-evolution@swift.org> wrote:
Am 09.06.2016 um 19:50 schrieb Thorsten Seitz via swift-evolution <swift-evolution@swift.org>:
Am 09.06.2016 um 18:49 schrieb Dave Abrahams via swift-evolution <swift-evolution@swift.org>:
on Wed Jun 08 2016, Jordan Rose <swift-evolution@swift.org> wrote:
On Jun 8, 2016, at 13:16, Dave Abrahams via swift-evolution >>>>>>>>> <swift-evolution@swift.org> wrote:
on Wed Jun 08 2016, Thorsten Seitz >>>>>>>> >>>>>>>>> <swift-evolution@swift.org >>>>>>>>> <mailto:swift-evolution@swift.org>> >>>>>>>>> wrote:
(One caveat exists with subclasses where the superclasses didn’t implement the method because I then am not allowed to `override` the default method but that is a bug IMO).
I also really like how extensions and conformance mix together in swift to bake retro-modelling in and the adapter pattern (spent enough years deep diving inside eclipse to appreciate it).
I would have preferred a unified model using just classes with real multiple inheritance like Eiffel has and value types just being a part of that similar to Eiffel’s `expanded` classes. But that ship has probably sailed a long time ago :-/
I like extensions very much (having used Smalltalk for a long time). I like enums for the pattern matching and structs as value types. But having to split protocols off instead of using abstract classes makes things more complicated IMO.
-1 i am old school c/c++... i really like protocol, struct, class, extensions, enums. It is a really nice mix that gives objc people room to grow, but I do miss how they are an integral part of generics (i protocols as a replacement and look forward to when they interact better) and namespaces+scoped-imports (c#)... Looking forward to where things go next
Yeah, namespacing/submodules/conflict resolution (when doing imports but also when conforming to multiple protocols which has just the same problems) are still missing. But I’m optimistic :-) Let’s complete generics first, then tackle existentials/type intersections.
I think I care more about existentials, but only because of the kind of java i wrote for a living. I just looked at a bunch of opensource swift libs (particularly for server side swift)... some of it is a real engineering disaster: 20+ folders, each with 2 source files... or 3 folders "extensions" "utils" "classes". Swift is currently not equiped for people to write big things with... I wish the team would address it sooner than later (look at their own c++ code to see the difference). IMHO import conflicts are more often the symptom of bad code than a real issue.
Alas, most languages suffer from poor module systems. A good module system should not only allow resolving conflicts between modules and making it possible to structure code well, but solve the issue around versioned modules so that is is possibly to safely use different versions of the same module in the same application.
-Thorsten
Cheers
-THorsten
So be it. But at least there should be no reasons for POP vs OOP wars ;-)
(I’d like to add that I liked Dave’s talks at last WWDC very much, it’s just that I don’t think that POP is something new or different.)
I used to thin that way. But today I think that although in broad brush strokes the similarities and bigger than the differences, there is room for making a bigger difference in the how.
This has been a point of confusion for me as well. I keep hearing that associated types are different from generic protocols, but this seems like a distinction without a difference.
Suppose Swift allowed generic protocols. How would a hypothetical Collection<Foo> be different in practice from the proposed existential Any<Collection where .Element == Foo>?
If Collection were a Java-like generic protocol you would have 5 generic parameters, all of which must be explicitly provided with arguments when forming an existential, although the arguments could be wildcards. This is a bit unwieldy.
Yes. This was #2 on my list. A very, very nice syntactic difference — nonrepetition of redundant type information is so much of what makes Swift great. — but only a syntactic difference.
Yes, in the realm of type theory and compiler internals they might represented differently, sure. But in practice, in terms of what code can actually do? I know of only two differences:
1. A type can only conform to any given protocol with one set of type parameters. (Nothing can be both Collection<Foo> and Collection<Bar>.)
This is a pretty huge difference. Multiple conformances are on the unlikely list for Swift and generic protocol syntax implies multiple conformances are possible (as is the case in at least some languages).
Right. Is that it? Are associated types really just generic protocols + single conformance constraint + type params inferred / implied?
P
···
On Jun 16, 2016, at 10:50 AM, Matthew Johnson <matthew@anandabits.com> wrote:
On Jun 16, 2016, at 10:36 AM, Paul Cantrell via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
You can recover an associated type (say, X.Element) by just having the type “X”, but this is not true for a type parameter. That doesn’t matter when you have (or want to specify) that type… for example, your comment that Collection<Foo> and Any<Collection where .Element == Foo> would basically be the same thing.
However, with generalized/enhanced existentials you would be able to write
but that’s not something we have now and doesn’t really generalize well in Swift.
- Doug
···
On Jun 16, 2016, at 9:46 AM, Thorsten Seitz via swift-evolution <swift-evolution@swift.org> wrote:
Am 16.06.2016 um 17:36 schrieb Paul Cantrell <cantrell@pobox.com <mailto:cantrell@pobox.com>>:
On Jun 16, 2016, at 8:29 AM, Thorsten Seitz via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
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.
This has been a point of confusion for me as well. I keep hearing that associated types are different from generic protocols, but this seems like a distinction without a difference.
Suppose Swift allowed generic protocols. How would a hypothetical Collection<Foo> be different in practice from the proposed existential Any<Collection where .Element == Foo>?
Yes, in the realm of type theory and compiler internals they might represented differently, sure. But in practice, in terms of what code can actually do? I know of only two differences:
1. A type can only conform to any given protocol with one set of type parameters. (Nothing can be both Collection<Foo> and Collection<Bar>.)
2. When a type conforms to Collection, it declares “associatedtype Foo” instead of “: Collection<Foo>”, and Foo can be inferred by the compiler in some circumstances. That’s handy, but it’s a syntactic difference.
That syntactic difference is *very* handy IMO for the following reason: with generics I have to repeat all types over and over again which gets ugly when I have levels of nesting where type parameters are constrained by other generics, which requires adding their parameters to the parameter list. Essentially the nested parameters have to be fully flattened because each type parameter has to be explicitly specified.
I’ll try to show that with a simplified example:
// with associated types
protocol Edge {
associatedtype VertexType
var source: VertexType { get }
var target: VertexType { get }
}
protocol Graph {
associatedtype EdgeType : Edge
var vertices: [EdgeType.VertexType] { get }
var edges: [EdgeType] { get }
Note, how the parameter list for GraphIterator exploded, because I had to list each level of nested types down to the VertexType, whereas
in the associated types example the GraphIterator simply declares an associated type conforming to the topmost type of my nesting, the Graph.
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 :-)
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?
···
On Jun 25, 2016, at 6:34 PM, Thorsten Seitz via swift-evolution <swift-evolution@swift.org> wrote:
Am 17.06.2016 um 19:04 schrieb Dave Abrahams <dabrahams@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@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@swift.org>:
Am 09.06.2016 um 18:49 schrieb Dave Abrahams via swift-evolution <swift-evolution@swift.org>:
on Wed Jun 08 2016, Jordan Rose <swift-evolution@swift.org> wrote:
On Jun 8, 2016, at 13:16, Dave Abrahams via swift-evolution >>>>>>>>> <swift-evolution@swift.org> wrote:
on Wed Jun 08 2016, Thorsten Seitz >>>>>>>> >>>>>>>>> <swift-evolution@swift.org >>>>>>>>> <mailto:swift-evolution@swift.org>> >>>>>>>>> wrote:
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:
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`.
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!
AFAIK Swift does not support structural types and I am not sure whether we should change that. In that case `Any` would become magic, yes.
-Thorsten
···
Am 11.06.2016 um 14:23 schrieb Brent Royal-Gordon <brent@architechies.com>:
The only magic would be that all type definitions (`protocol` etc.) which do not give a supertype they conform to, will implicitly conform to `Any`, i.e.
protocol Foo { … }
means
protocol Foo : Any { … }
Any is also the supertype of all structural types, and structural types cannot conform to protocols.
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:
(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.
As you know I like using `&` as type intersection operator. But you write "The syntax leave a void when it comes to expressing the so called Top type:“
Why? Just give the top type a name, e.g. `Any` and you are done.
Yes.. I just don't like magic. I think that all types should be expressable with a syntax and that you can then decide to alias one parsing case with a specific name. Not the other way around.
Well, I think that would be backwards, because we have a nominal type system. That means that the syntax for a type is just its name.
I realize we do not understand each other.
The words we use now to describe a behavior are just that, words. As is the concepts they describe are useless to the computer running the compiler.
So we need to map these concepts into a heuristic that a fast but dumb computer will be able to reason with. The code to do that will be either clean and organized, or it will look contrived and full of different paths that will be difficult to mesh together. Of all the possible ways in which swift can behave as a language, some will lead to the former, others to the latter code. I think this is not fate or something you find out after the decision was made and you struggle to carry it through. This is something that can partially be predicted. One of the tools for such prediction is to translate the concepts into a grammar that will show formalize the logic. The simpler the logic, the cleaner (not simple) the final code.
Saying that the syntax for a type is a name is of no use whatsoever for the compiler implementer. It is so universally true that it cannot help in any way whatsoever decide the shape of the swift grammar, much less the structure of the c++ code implementing it.
`&` is a type operator which creates a new type from its operands.
`where` clauses add constraints to types.
But it all starts with a type's name.
The only magic would be that all type definitions (`protocol` etc.) which do not give a supertype they conform to, will implicitly conform to `Any`, i.e.
I call magic the core notions of swift that cannot be expressed in swift but have to parachutted in by the compiler. If you read Chris' initial message you will see that he said as much, that adopting P&Q as syntax was leaving a gap that the compiler would have to magically fill.
protocol Foo { … }
means
protocol Foo : Any { … }
That’s less magic than creating a special syntax just to express the top type.
I will try again... Would it be easier to understand it if instead of magic I said arbitrary? You are creating a special case: you decide arbitrarily that the special series of characters 'A' 'n' 'y' with this precise casing and without any spaces is going to be adorned with a special meaning when used in specific situations. This is not something you deduced. My approach is exactly the opposite: _ does not describe the top type because I have a special affinity with these characters, it describes the top type because it is the only possible logical conclusion from having followed a set of rules attached to a syntax that describes ALL existentials. And because it is not particularly savory, I typealias it to something else. But this is not in the compiler, it us in the standard linrary... inside swift, not outside.
Ok, that's a good argument. But your proposal does not contain those rules.
Why should _ describe the top type? You just say this in your proposal, but you do not define rules what _ means.
_[A,B] describes the type intersection of A and B, i.e. it contains all members of A which are also members of B. _ does not list any types, so one might conclude that it has no members and therefore must be the bottom type!
Or _[A,B] in reality means _[A,B,Any], i.e. A & B & Any which of course is equal to A & B. Then _ would just mean Any, but we would have had to introduce the top type explicitly again.
On the other hand if we declare a protocol Any and define protocol A {...} to mean protocol A: Any {...} there is not really something special in the compiler except for adding the conformance which seems very minor to me. Everything else is just standard behavior of the type system.
Read Chris' original announcement.. He describes P&Q as it would be adopted today as being just a CORNER CASE of a single general principal... a single grammar that can work today to describe P&Q as well as generalize existentials tomorrow. No special treatment, single parser, single rule. I don't like to write un-necessary IF statements in code.
Sorry, I do not understand what you mean here: where would you have to write unnecessary if-statements?
Why should the top type have special *syntax*? It is just the type all other types conform to. No need to do something special here and therefore no need to invent an alternative syntax like `Any<>` or the alternative from your gist which is rather confusing IMO (and I don’t like the special case given to classes in the syntax).
The _ case is just a degenerate case of the syntax, showing that it is expressible inside, as opposoed to have to define a contextual keyword (read the SourceKit code). And then it is aliasable. Part of the problems in swift today to me is that some things are no doable in swift, so the compiler must contain that semantic. This here is just one example, but there are others. These are notions that the standard library has to defer to the compiler. Some of them have been tabled for 4.0 it seems. My point here was that it is not fate, and choosing the syntax carefully for existentials (whichever it is, i don't really care inthe end) would prevent having to add a magic keyword for to top type to the compiler and to SourceKit again (they are trying to remove them).
`Any` would *not* be a keyword. It is just a type name like `Collection` or `Int`. Like I said, the only magic would be in adding `: Any` to type definitions without a conforming clause.
I hope that by now you understand what magic I was talking about.
As for the current Any<...> proposal for generalizing existentials it is IMHO cluncky and magic. There is nothing stopping us mechanically from entering Any<UITableView, UIButton>. To me that is the holemark of bad design. In what I tested you just can't do it and the reason is clear. Of course when I
`Any<UITableView, UIButton>` is just an intersection type and would be written UITableView & UIButton. And, yes, that would be ok, because it would be just the empty set, i.e. the bottom type (which has no members). There is no magic involved, it is just the normal result of an intersection: each type is a set containing all instances of this type (instances conforming to it)
You can't be serious? You are saying that to you the ide should not be telling us we are writting an absurdity? And this one was obvious, but it gets a lot worse with more complex types.
Why is an empty intersection absurd? The beauty is that all type expressions sort out automatically by applying simple set rules. And it actually does make sense!
struct Set<T> {
// answer any one element
func any() -> T?
}
func intersect<T, U>(a: Set<T>, b: Set<U>) -> Set<T & U> {...}
let x: Set<UITableView> = ...
let y: Set<UIButton> = ..
let z = intersect(x, y) // has type Set<Bottom>
z.any // has type Optional<Bottom> and therefore can only be nil
Therefore the type system statically knows that the intersection of those sets is always empty. No need to declare this as invalid. It just works correctly and gives the results you would expect without need for special casing and compiler magic (which we both would like to keep small).
-Thorsten
···
Am 11.06.2016 um 15:08 schrieb L. Mihalkovic <laurent.mihalkovic@gmail.com>:
On Jun 11, 2016, at 2:05 PM, Thorsten Seitz <tseitz42@icloud.com> wrote:
Am 11.06.2016 um 12:38 schrieb L. Mihalkovic <laurent.mihalkovic@gmail.com>:
On Jun 11, 2016, at 11:30 AM, Thorsten Seitz <tseitz42@icloud.com> wrote:
Am 11.06.2016 um 08:00 schrieb L. Mihalkovic <laurent.mihalkovic@gmail.com>:
On Jun 10, 2016, at 9:35 PM, Thorsten Seitz via swift-evolution <swift-evolution@swift.org> wrote:
Am 09.06.2016 um 19:50 schrieb Thorsten Seitz via swift-evolution <swift-evolution@swift.org>:
Am 09.06.2016 um 18:49 schrieb Dave Abrahams via swift-evolution <swift-evolution@swift.org>:
on Wed Jun 08 2016, Jordan Rose <swift-evolution@swift.org> wrote:
On Jun 8, 2016, at 13:16, Dave Abrahams via swift-evolution >>>>>>>>>> <swift-evolution@swift.org> wrote:
on Wed Jun 08 2016, Thorsten Seitz >>>>>>>>> >>>>>>>>>> <swift-evolution@swift.org >>>>>>>>>> <mailto:swift-evolution@swift.org>> >>>>>>>>>> wrote:
. Intersecting two sets might result in an empty set. The type denoting the empty set is the bottom type which is the subtype of all types and might be called `Nothing` or `Never` or `Bottom`.
Ceylon makes very nice use of type intersections and type unions and the beautiful thing is that these type operations really just work like you would expect if you think of types as sets (which is the standard definition for a type AFAIK). No surprises and no magic there!
So it is *not* a sign of bad design, quite to the contrary! What did you test it with? Probably not Ceylon, because otherwise you would have seen that it just works.
Hey, who knows, ceylon may one day come to llvm...
say you can't I do not mean that your keyboard will zap u if you try. But the way to verify correctness is different... and in a world where xcode would be a good ide, then code completion would even show you why and actually truly assist (because the grammar i tested makes really makes it possible). I know that xcode is only as good as SourceKit lets it be.
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).
Likewise `Any<Collection where .Element: Number>` is just the same as `Collection<? extends Number>` in Java.
Java’s wildcards are a way to express use-site variance. The proposed `Any<>` does just the same.
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.
This picture is accurate today, but there are going to be more serious differences after 10 no date is currently geven for when it will come)
You mean Java 10?
Yes. After 10. Than is their only date hint.
In essence protocols with associated types are like generics with wildcards.
Yes, java has kept everything within its generics system rather than split parts out. Something people may not immediately think about with respect to the 2 generic systems is that when u call a func<T>capture(T t){} in java with a wildcard you are doing a compile time capture only (to avoid the dreaded unsafe casts), whereas it is really nice to do the same in swift and subsequently be able to access T.Type and see that it is not Any. The closest u ever get to that type at runtime in java is via generics introspection, but u still can't do everything ( like no new T() ). But today the bridging between existential types and generics is definitely a work in progress.
Coming back to the questions whether (a) allowing existentials to be used as 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)
IMO they made java 8 (referring to streams). And even though the syntax for co/contra variance is pretty heavy, it is the foundation for all modern java code. The any-fication of the generics is going to open new doors as some of it will translate into a partial reification in the jvm. It seems the decision for now is to not use the extra info in java to retain binary compatibility with all the erased code out there, this is something scala might use in areas where it won't mind loosing java compatibility.
(b) no, sacrificing type safety does not make sense, as the experience with Java’s wildcards shows that this is not needed. 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.
I hope typesafe opening inside the " if let " syntax gets added. I know that chris is against sugaring, but I played if an implementation of
... x is String?
If let! x {}
That runs as
if let x = x {}
something equally short could be done here.
(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). The more important distinction of Swift is emphasizing value types and making mutation safely available by enforcing copy semantics for value types.
Values are coming to the jvm, which will narrow this gap (like their view identity for value vs ref and the whole deep ==) . I also really like the cow approach of the swift runtime.
But protocols are not really different from interfaces in Java.
There is one big difference: default methods, but it seems swift will add that soon.
Swift already has default extension methods, doesn’t it?
Yes, and no. It has differently dispatched code that can be found to fill in the gap of the conformance req, yes. But Joe Grof (?) said that there are no reasons why these could not be added. Having true defaults could be one way to deal with optional comformance…
The dispatch issue only arises for extensions introducing a method that is not declared in a protocol. That is something you *cannot* do in Java. Java’s default methods are implementations for methods declared in interfaces. Swift’s extension methods providing defaults for methods declared in a protocol are dynamically dispatched and should work like Java’s default methods.
I think we should agree to disagree.
(One caveat exists with subclasses where the superclasses didn’t implement the method because I then am not allowed to `override` the default method but that is a bug IMO).
I also really like how extensions and conformance mix together in swift to bake retro-modelling in and the adapter pattern (spent enough years deep diving inside eclipse to appreciate it).
I would have preferred a unified model using just classes with real multiple inheritance like Eiffel has and value types just being a part of that similar to Eiffel’s `expanded` classes. But that ship has probably sailed a long time ago :-/
I like extensions very much (having used Smalltalk for a long time). I like enums for the pattern matching and structs as value types. But having to split protocols off instead of using abstract classes makes things more complicated IMO.
-1 i am old school c/c++... i really like protocol, struct, class, extensions, enums. It is a really nice mix that gives objc people room to grow, but I do miss how they are an integral part of generics (i protocols as a replacement and look forward to when they interact better) and namespaces+scoped-imports (c#)... Looking forward to where things go next
Yeah, namespacing/submodules/conflict resolution (when doing imports but also when conforming to multiple protocols which has just the same problems) are still missing. But I’m optimistic :-) Let’s complete generics first, then tackle existentials/type intersections.
I think I care more about existentials, but only because of the kind of java i wrote for a living. I just looked at a bunch of opensource swift libs (particularly for server side swift)... some of it is a real engineering disaster: 20+ folders, each with 2 source files... or 3 folders "extensions" "utils" "classes". Swift is currently not equiped for people to write big things with... I wish the team would address it sooner than later (look at their own c++ code to see the difference). IMHO import conflicts are more often the symptom of bad code than a real issue.
Alas, most languages suffer from poor module systems. A good module system should not only allow resolving conflicts between modules and making it possible to structure code well, but solve the issue around versioned modules so that is is possibly to safely use different versions of the same module in the same application.
-Thorsten
Cheers
-THorsten
So be it. But at least there should be no reasons for POP vs OOP wars ;-)
(I’d like to add that I liked Dave’s talks at last WWDC very much, it’s just that I don’t think that POP is something new or different.)
I used to thin that way. But today I think that although in broad brush strokes the similarities and bigger than the differences, there is room for making a bigger difference in the how.
Right. Is that it? Are associated types really just generic protocols + single conformance constraint + type params inferred / implied?
A protocol and its associated types form a "package" or "group" of types that function together as one cohesive unit. For example, in the Swift world a collection is defined not only by its element type, but also by the type of its subsequence and its index. The protocol and associated types together define how the various types in the group relate to each other (via associated type nesting; for example Element belongs to Iterator belongs to Sequence), and to other types (via protocol conformance constraints).
A type doesn't need to be generic to adopt a protocol + associated types. For example, the various String views are bona fide Collections, but they're not generic in any way: all their associated types are fixed to concrete types as far as I can tell.
Another way to think of it is that generics allow *instances* to be abstracted over type, while protocols+associated types allow *conforming types* to be abstracted over type. For example, Either<T, U> can be instantiated for one instance as Either<String, Int>, and for another as Either<NSView, Bool>. (A simplified example version of the) protocol Collection can be instantiated for one type as being (Collection.Element = Int, Collection.Index = FooIndex), and another type as being (Collection.Element = String, Collection.Index = Int).
A generic type conforming to Collection would "unify" the two concepts. For example, SimpleArray<T> instantiates Collection as being (Collection.Element = T, Collection.Index = Int). Then, individual instances of SimpleArray<T> abstract over T by filling in T with Int (SimpleArray<Int>), String, etc.
Hope that helps,
Austin
···
On Jun 16, 2016, at 8:55 AM, Paul Cantrell via swift-evolution <swift-evolution@swift.org> wrote:
With Austin’s proposal we could even recover the associated type of a concrete instance as a path dependent type, making this well typed, for example:
heterogeneousArrayOfCollections.map { xs in xs.min().map { minElement in xs.index(of: minElement) } } // minElement has type xs.Iterator.Element
-Thorsten
···
Am 17.06.2016 um 16:11 schrieb Douglas Gregor <dgregor@apple.com>:
On Jun 16, 2016, at 9:46 AM, Thorsten Seitz via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
Am 16.06.2016 um 17:36 schrieb Paul Cantrell <cantrell@pobox.com <mailto:cantrell@pobox.com>>:
On Jun 16, 2016, at 8:29 AM, Thorsten Seitz via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
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.
This has been a point of confusion for me as well. I keep hearing that associated types are different from generic protocols, but this seems like a distinction without a difference.
Suppose Swift allowed generic protocols. How would a hypothetical Collection<Foo> be different in practice from the proposed existential Any<Collection where .Element == Foo>?
Yes, in the realm of type theory and compiler internals they might represented differently, sure. But in practice, in terms of what code can actually do? I know of only two differences:
1. A type can only conform to any given protocol with one set of type parameters. (Nothing can be both Collection<Foo> and Collection<Bar>.)
2. When a type conforms to Collection, it declares “associatedtype Foo” instead of “: Collection<Foo>”, and Foo can be inferred by the compiler in some circumstances. That’s handy, but it’s a syntactic difference.
That syntactic difference is *very* handy IMO for the following reason: with generics I have to repeat all types over and over again which gets ugly when I have levels of nesting where type parameters are constrained by other generics, which requires adding their parameters to the parameter list. Essentially the nested parameters have to be fully flattened because each type parameter has to be explicitly specified.
I’ll try to show that with a simplified example:
// with associated types
protocol Edge {
associatedtype VertexType
var source: VertexType { get }
var target: VertexType { get }
}
protocol Graph {
associatedtype EdgeType : Edge
var vertices: [EdgeType.VertexType] { get }
var edges: [EdgeType] { get }
Note, how the parameter list for GraphIterator exploded, because I had to list each level of nested types down to the VertexType, whereas
in the associated types example the GraphIterator simply declares an associated type conforming to the topmost type of my nesting, the Graph.
Is there a deeper difference I’m missing?
Maybe Dave can chime in here?
You can recover an associated type (say, X.Element) by just having the type “X”, but this is not true for a type parameter. That doesn’t matter when you have (or want to specify) that type… for example, your comment that Collection<Foo> and Any<Collection where .Element == Foo> would basically be the same thing.
However, with generalized/enhanced existentials you would be able to write
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 :-)
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?
e.g. Ceylon and Scala with their definition-site variance declarations, Ceylon with defaults for type parameters, Scala with lower type bounds, Scala and Haskell with higher kinded types, Haskell with type classes, type families, kind polymorphism and lots more. Then there are things like types parameterized by values (like discussed on this list in the thread about expanding generics to support defining a type safe unit system).
The design space is much larger than the mainstream, especially something like Java, makes on believe… :-)
-Thorsten
···
Am 25.06.2016 um 19:09 schrieb L. Mihalkovic <laurent.mihalkovic@gmail.com>:
On Jun 25, 2016, at 6:34 PM, Thorsten Seitz via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
Am 17.06.2016 um 19:04 schrieb Dave Abrahams <dabrahams@apple.com <mailto:dabrahams@apple.com>>:
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@apple.com <mailto:dabrahams@apple.com>>:
on Fri Jun 10 2016, Thorsten Seitz <tseitz42-AT-icloud.com <http://tseitz42-at-icloud.com/>> wrote:
Am 09.06.2016 um 19:50 schrieb Thorsten Seitz via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>>:
Am 09.06.2016 um 18:49 schrieb Dave Abrahams via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>>:
on Wed Jun 08 2016, Jordan Rose <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
On Jun 8, 2016, at 13:16, Dave Abrahams via swift-evolution >>>>>>>>>> <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
on Wed Jun 08 2016, Thorsten Seitz >>>>>>>>> >>>>>>>>>> <swift-evolution@swift.org <mailto:swift-evolution@swift.org> >>>>>>>>>> <mailto:swift-evolution@swift.org <mailto:swift-evolution@swift.org>>> >>>>>>>>>> wrote:
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:
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`.
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!
The only magic would be that all type definitions (`protocol` etc.) which do not give a supertype they conform to, will implicitly conform to `Any`, i.e.
protocol Foo { … }
means
protocol Foo : Any { … }
Any is also the supertype of all structural types, and structural types cannot conform to protocols.
AFAIK Swift does not support structural types and I am not sure whether we should change that. In that case `Any` would become magic, yes.
Functions and tuples are structural types, although it is probably possible to make tuples syntactic sugar for a Tuple type of we get the necessary variadic generics support.
···
Sent from my iPad
On Jun 11, 2016, at 7:31 AM, Thorsten Seitz via swift-evolution <swift-evolution@swift.org> wrote:
Am 11.06.2016 um 14:23 schrieb Brent Royal-Gordon <brent@architechies.com>:
The only magic would be that all type definitions (`protocol` etc.) which do not give a supertype they conform to, will implicitly conform to `Any`, i.e.
protocol Foo { … }
means
protocol Foo : Any { … }
Any is also the supertype of all structural types, and structural types cannot conform to protocols.
AFAIK Swift does not support structural types and I am not sure whether we should change that. In that case `Any` would become magic, yes.
Hmmm poor tuples... I thought they did a good job as swift's structural type.
···
On Jun 11, 2016, at 2:31 PM, Thorsten Seitz <tseitz42@icloud.com> wrote:
Am 11.06.2016 um 14:23 schrieb Brent Royal-Gordon <brent@architechies.com>:
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:
(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.
As you know I like using `&` as type intersection operator. But you write "The syntax leave a void when it comes to expressing the so called Top type:“
Why? Just give the top type a name, e.g. `Any` and you are done.
Yes.. I just don't like magic. I think that all types should be expressable with a syntax and that you can then decide to alias one parsing case with a specific name. Not the other way around.
Well, I think that would be backwards, because we have a nominal type system. That means that the syntax for a type is just its name.
I realize we do not understand each other.
The words we use now to describe a behavior are just that, words. As is the concepts they describe are useless to the computer running the compiler.
So we need to map these concepts into a heuristic that a fast but dumb computer will be able to reason with. The code to do that will be either clean and organized, or it will look contrived and full of different paths that will be difficult to mesh together. Of all the possible ways in which swift can behave as a language, some will lead to the former, others to the latter code. I think this is not fate or something you find out after the decision was made and you struggle to carry it through. This is something that can partially be predicted. One of the tools for such prediction is to translate the concepts into a grammar that will show formalize the logic. The simpler the logic, the cleaner (not simple) the final code.
Saying that the syntax for a type is a name is of no use whatsoever for the compiler implementer. It is so universally true that it cannot help in any way whatsoever decide the shape of the swift grammar, much less the structure of the c++ code implementing it.
`&` is a type operator which creates a new type from its operands.
`where` clauses add constraints to types.
But it all starts with a type's name.
The only magic would be that all type definitions (`protocol` etc.) which do not give a supertype they conform to, will implicitly conform to `Any`, i.e.
I call magic the core notions of swift that cannot be expressed in swift but have to parachutted in by the compiler. If you read Chris' initial message you will see that he said as much, that adopting P&Q as syntax was leaving a gap that the compiler would have to magically fill.
protocol Foo { … }
means
protocol Foo : Any { … }
That’s less magic than creating a special syntax just to express the top type.
I will try again... Would it be easier to understand it if instead of magic I said arbitrary? You are creating a special case: you decide arbitrarily that the special series of characters 'A' 'n' 'y' with this precise casing and without any spaces is going to be adorned with a special meaning when used in specific situations. This is not something you deduced. My approach is exactly the opposite: _ does not describe the top type because I have a special affinity with these characters, it describes the top type because it is the only possible logical conclusion from having followed a set of rules attached to a syntax that describes ALL existentials. And because it is not particularly savory, I typealias it to something else. But this is not in the compiler, it us in the standard linrary... inside swift, not outside.
Ok, that's a good argument. But your proposal does not contain those rules.
Why should _ describe the top type? You just say this in your proposal, but you do not define rules what _ means.
_[A,B]
don't know where this comes from. This is incorrect and the parser would reject it.
describes the type intersection of A and B, i.e. it contains all members of A which are also members of B. _ does not list any types, so one might conclude that it has no members and therefore must be the bottom type!
Are you saying that protocol<> is the bottom type of Swift then? Because if you disect the logic it follows to explain why it is a top type and apply the exact same reasoning to _ then you should reach the same conclusion {same effects produce the same conclusions}
Or _[A,B] in reality means _[A,B,Any],i.e. A & B & Any which of course is equal to A & B. Then _ would just mean Any, but we would have had to introduce the top type explicitly again.
From cause to effects. Any is an effect (a conclusion we reach) rather than a cause (something we posit before starting the discussion). Like I explained, Any is a stdlib provided convenient typealias for _ as opposed to a parachuted concept born inside the compiler. Btw, it means a different stdlib could redefine it as All, without altering the compiler. Any is not core to swift's structure, but _ is because it is borne out of the mechanical uniformity of the grammar.
On the other hand if we declare a protocol Any and define protocol A {...} to mean protocol A: Any {...} there is not really something special in the compiler except for adding the conformance which seems very minor to me. Everything else is just standard behavior of the type system.
You are back to adding some magic that I just demonstrated is not required if the grammar is simple (not that i could add it to my grammar to please you, but then it would not eliminate any of the other definitions, and would just complicate the parser and type checker to ensure that people use it correctly. Per Ockham's principle, the system i describe is a better alternative.
Read Chris' original announcement.. He describes P&Q as it would be adopted today as being just a CORNER CASE of a single general principal... a single grammar that can work today to describe P&Q as well as generalize existentials tomorrow. No special treatment, single parser, single rule. I don't like to write un-necessary IF statements in code.
Sorry, I do not understand what you mean here: where would you have to write unnecessary if-statements?
Close your eyes and make a mental representation of what the code to implement the Any<....> proposal will necessarily look like... that is what I am referring to. That code cannot not be uggly. Because some of of things a parser typically should catch are not discernsble, and therefore have to be differed to the type checker. By comparison, look at the grammar I wrote and picture the code to implement it. The picture will look very different, and the parser can already eliminate a lot of things that never reach the type checker.
Why should the top type have special *syntax*? It is just the type all other types conform to. No need to do something special here and therefore no need to invent an alternative syntax like `Any<>` or the alternative from your gist which is rather confusing IMO (and I don’t like the special case given to classes in the syntax).
The _ case is just a degenerate case of the syntax, showing that it is expressible inside, as opposoed to have to define a contextual keyword (read the SourceKit code). And then it is aliasable. Part of the problems in swift today to me is that some things are no doable in swift, so the compiler must contain that semantic. This here is just one example, but there are others. These are notions that the standard library has to defer to the compiler. Some of them have been tabled for 4.0 it seems. My point here was that it is not fate, and choosing the syntax carefully for existentials (whichever it is, i don't really care inthe end) would prevent having to add a magic keyword for to top type to the compiler and to SourceKit again (they are trying to remove them).
`Any` would *not* be a keyword. It is just a type name like `Collection` or `Int`. Like I said, the only magic would be in adding `: Any` to type definitions without a conforming clause.
I hope that by now you understand what magic I was talking about.
As for the current Any<...> proposal for generalizing existentials it is IMHO cluncky and magic. There is nothing stopping us mechanically from entering Any<UITableView, UIButton>. To me that is the holemark of bad design. In what I tested you just can't do it and the reason is clear. Of course when I
`Any<UITableView, UIButton>` is just an intersection type and would be written UITableView & UIButton. And, yes, that would be ok, because it would be just the empty set, i.e. the bottom type (which has no members). There is no magic involved, it is just the normal result of an intersection: each type is a set containing all instances of this type (instances conforming to it)
You can't be serious? You are saying that to you the ide should not be telling us we are writting an absurdity? And this one was obvious, but it gets a lot worse with more complex types.
Take a screenshot next time you see a "let x:Any<UITableView, UIButton>" in an app and send it to me. The absurdity would be for the compiler to generate a binary, the app to start, and us to wonder why x the code depending on x would never execute. Seems obvious to you why now?
not debatting past here I can't see how it relates (it is interesting but you may want to look at the whole thing from a 'whats usefull' perspective, rather than as 'whats combinatorially possible'
···
On Jun 11, 2016, at 11:43 PM, Thorsten Seitz <tseitz42@icloud.com> wrote:
Am 11.06.2016 um 15:08 schrieb L. Mihalkovic <laurent.mihalkovic@gmail.com>:
On Jun 11, 2016, at 2:05 PM, Thorsten Seitz <tseitz42@icloud.com> wrote:
Am 11.06.2016 um 12:38 schrieb L. Mihalkovic <laurent.mihalkovic@gmail.com>:
On Jun 11, 2016, at 11:30 AM, Thorsten Seitz <tseitz42@icloud.com> wrote:
Am 11.06.2016 um 08:00 schrieb L. Mihalkovic <laurent.mihalkovic@gmail.com>:
On Jun 10, 2016, at 9:35 PM, Thorsten Seitz via swift-evolution <swift-evolution@swift.org> wrote:
Am 09.06.2016 um 19:50 schrieb Thorsten Seitz via swift-evolution <swift-evolution@swift.org>:
Am 09.06.2016 um 18:49 schrieb Dave Abrahams via swift-evolution <swift-evolution@swift.org>:
on Wed Jun 08 2016, Jordan Rose <swift-evolution@swift.org> wrote:
On Jun 8, 2016, at 13:16, Dave Abrahams via swift-evolution >>>>>>>>>>> <swift-evolution@swift.org> wrote:
on Wed Jun 08 2016, Thorsten Seitz >>>>>>>>>> >>>>>>>>>>> <swift-evolution@swift.org >>>>>>>>>>> <mailto:swift-evolution@swift.org>> >>>>>>>>>>> wrote:
Why is an empty intersection absurd? The beauty is that all type expressions sort out automatically by applying simple set rules. And it actually does make sense!
struct Set<T> {
// answer any one element
func any() -> T?
}
func intersect<T, U>(a: Set<T>, b: Set<U>) -> Set<T & U> {...}
let x: Set<UITableView> = ...
let y: Set<UIButton> = ..
let z = intersect(x, y) // has type Set<Bottom>
z.any // has type Optional<Bottom> and therefore can only be nil
Therefore the type system statically knows that the intersection of those sets is always empty. No need to declare this as invalid. It just works correctly and gives the results you would expect without need for special casing and compiler magic (which we both would like to keep small).
-Thorsten
. Intersecting two sets might result in an empty set. The type denoting the empty set is the bottom type which is the subtype of all types and might be called `Nothing` or `Never` or `Bottom`.
Ceylon makes very nice use of type intersections and type unions and the beautiful thing is that these type operations really just work like you would expect if you think of types as sets (which is the standard definition for a type AFAIK). No surprises and no magic there!
So it is *not* a sign of bad design, quite to the contrary! What did you test it with? Probably not Ceylon, because otherwise you would have seen that it just works.
Hey, who knows, ceylon may one day come to llvm...
say you can't I do not mean that your keyboard will zap u if you try. But the way to verify correctness is different... and in a world where xcode would be a good ide, then code completion would even show you why and actually truly assist (because the grammar i tested makes really makes it possible). I know that xcode is only as good as SourceKit lets it be.
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).
Likewise `Any<Collection where .Element: Number>` is just the same as `Collection<? extends Number>` in Java.
Java’s wildcards are a way to express use-site variance. The proposed `Any<>` does just the same.
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.
This picture is accurate today, but there are going to be more serious differences after 10 no date is currently geven for when it will come)
You mean Java 10?
Yes. After 10. Than is their only date hint.
In essence protocols with associated types are like generics with wildcards.
Yes, java has kept everything within its generics system rather than split parts out. Something people may not immediately think about with respect to the 2 generic systems is that when u call a func<T>capture(T t){} in java with a wildcard you are doing a compile time capture only (to avoid the dreaded unsafe casts), whereas it is really nice to do the same in swift and subsequently be able to access T.Type and see that it is not Any. The closest u ever get to that type at runtime in java is via generics introspection, but u still can't do everything ( like no new T() ). But today the bridging between existential types and generics is definitely a work in progress.
Coming back to the questions whether (a) allowing existentials to be used as 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)
IMO they made java 8 (referring to streams). And even though the syntax for co/contra variance is pretty heavy, it is the foundation for all modern java code. The any-fication of the generics is going to open new doors as some of it will translate into a partial reification in the jvm. It seems the decision for now is to not use the extra info in java to retain binary compatibility with all the erased code out there, this is something scala might use in areas where it won't mind loosing java compatibility.
(b) no, sacrificing type safety does not make sense, as the experience with Java’s wildcards shows that this is not needed. 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.
I hope typesafe opening inside the " if let " syntax gets added. I know that chris is against sugaring, but I played if an implementation of
... x is String?
If let! x {}
That runs as
if let x = x {}
something equally short could be done here.
(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). The more important distinction of Swift is emphasizing value types and making mutation safely available by enforcing copy semantics for value types.
Values are coming to the jvm, which will narrow this gap (like their view identity for value vs ref and the whole deep ==) . I also really like the cow approach of the swift runtime.
But protocols are not really different from interfaces in Java.
There is one big difference: default methods, but it seems swift will add that soon.
Swift already has default extension methods, doesn’t it?
Yes, and no. It has differently dispatched code that can be found to fill in the gap of the conformance req, yes. But Joe Grof (?) said that there are no reasons why these could not be added. Having true defaults could be one way to deal with optional comformance…
The dispatch issue only arises for extensions introducing a method that is not declared in a protocol. That is something you *cannot* do in Java. Java’s default methods are implementations for methods declared in interfaces. Swift’s extension methods providing defaults for methods declared in a protocol are dynamically dispatched and should work like Java’s default methods.
I think we should agree to disagree.
(One caveat exists with subclasses where the superclasses didn’t implement the method because I then am not allowed to `override` the default method but that is a bug IMO).
I also really like how extensions and conformance mix together in swift to bake retro-modelling in and the adapter pattern (spent enough years deep diving inside eclipse to appreciate it).
I would have preferred a unified model using just classes with real multiple inheritance like Eiffel has and value types just being a part of that similar to Eiffel’s `expanded` classes. But that ship has probably sailed a long time ago :-/
I like extensions very much (having used Smalltalk for a long time). I like enums for the pattern matching and structs as value types. But having to split protocols off instead of using abstract classes makes things more complicated IMO.
-1 i am old school c/c++... i really like protocol, struct, class, extensions, enums. It is a really nice mix that gives objc people room to grow, but I do miss how they are an integral part of generics (i protocols as a replacement and look forward to when they interact better) and namespaces+scoped-imports (c#)... Looking forward to where things go next
Yeah, namespacing/submodules/conflict resolution (when doing imports but also when conforming to multiple protocols which has just the same problems) are still missing. But I’m optimistic :-) Let’s complete generics first, then tackle existentials/type intersections.
I think I care more about existentials, but only because of the kind of java i wrote for a living. I just looked at a bunch of opensource swift libs (particularly for server side swift)... some of it is a real engineering disaster: 20+ folders, each with 2 source files... or 3 folders "extensions" "utils" "classes". Swift is currently not equiped for people to write big things with... I wish the team would address it sooner than later (look at their own c++ code to see the difference). IMHO import conflicts are more often the symptom of bad code than a real issue.
Alas, most languages suffer from poor module systems. A good module system should not only allow resolving conflicts between modules and making it possible to structure code well, but solve the issue around versioned modules so that is is possibly to safely use different versions of the same module in the same application.
-Thorsten
Cheers
-THorsten
So be it. But at least there should be no reasons for POP vs OOP wars ;-)
(I’d like to add that I liked Dave’s talks at last WWDC very much, it’s just that I don’t think that POP is something new or different.)
I used to thin that way. But today I think that although in broad brush strokes the similarities and bigger than the differences, there is room for making a bigger difference in the how.
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.
This has been a point of confusion for me as well. I keep hearing that associated types are different from generic protocols, but this seems like a distinction without a difference.
Suppose Swift allowed generic protocols. How would a hypothetical Collection<Foo> be different in practice from the proposed existential Any<Collection where .Element == Foo>?
Yes, in the realm of type theory and compiler internals they might represented differently, sure. But in practice, in terms of what code can actually do? I know of only two differences:
1. A type can only conform to any given protocol with one set of type parameters. (Nothing can be both Collection<Foo> and Collection<Bar>.)
2. When a type conforms to Collection, it declares “associatedtype Foo” instead of “: Collection<Foo>”, and Foo can be inferred by the compiler in some circumstances. That’s handy, but it’s a syntactic difference.
That syntactic difference is *very* handy IMO for the following reason: with generics I have to repeat all types over and over again which gets ugly when I have levels of nesting where type parameters are constrained by other generics, which requires adding their parameters to the parameter list. Essentially the nested parameters have to be fully flattened because each type parameter has to be explicitly specified.
I’ll try to show that with a simplified example:
// with associated types
protocol Edge {
associatedtype VertexType
var source: VertexType { get }
var target: VertexType { get }
}
protocol Graph {
associatedtype EdgeType : Edge
var vertices: [EdgeType.VertexType] { get }
var edges: [EdgeType] { get }
Note, how the parameter list for GraphIterator exploded, because I had to list each level of nested types down to the VertexType, whereas
in the associated types example the GraphIterator simply declares an associated type conforming to the topmost type of my nesting, the Graph.
Is there a deeper difference I’m missing?
Maybe Dave can chime in here?
You can recover an associated type (say, X.Element) by just having the type “X”, but this is not true for a type parameter. That doesn’t matter when you have (or want to specify) that type… for example, your comment that Collection<Foo> and Any<Collection where .Element == Foo> would basically be the same thing.
However, with generalized/enhanced existentials you would be able to write
Having to add wildcards means yet another advanced generics feature. I suspect that we can make generalized existentials sufficiently accessible for all Swift programmers. Not so sure one could do that with generic protocols and wildcards (Java's experience with wildcards is not encouraging).
- Doug
···
Sent from my iPhone
On Jun 18, 2016, at 2:29 AM, Thorsten Seitz <tseitz42@icloud.com> wrote:
Am 17.06.2016 um 16:11 schrieb Douglas Gregor <dgregor@apple.com>:
On Jun 16, 2016, at 9:46 AM, Thorsten Seitz via swift-evolution <swift-evolution@swift.org> wrote:
Am 16.06.2016 um 17:36 schrieb Paul Cantrell <cantrell@pobox.com>:
On Jun 16, 2016, at 8:29 AM, Thorsten Seitz via swift-evolution <swift-evolution@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 :-)
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?
e.g. Ceylon and Scala with their definition-site variance declarations, Ceylon with defaults for type parameters, Scala with lower type bounds, Scala and Haskell with higher kinded types, Haskell with type classes, type families, kind polymorphism and lots more. Then there are things like types parameterized by values (like discussed on this list in the thread about expanding generics to support defining a type safe unit system).
this was just about which generics you were familiar with, and were comparing with Java's. Considering how you seemed to indicate how puny the latter is compared to others i was expecting a more light-bulb-moment answer. I would have added kotlin and c# especially considering how their their definition-site syntax is the same. But more so kotlin if i had to name a type system with generics really dwarfing java's. :-)
The design space is much larger than the mainstream, especially something like Java, makes on believe… :-)
... or ceylon ;-)
···
On Jun 25, 2016, at 8:48 PM, Thorsten Seitz <tseitz42@icloud.com> wrote:
Am 25.06.2016 um 19:09 schrieb L. Mihalkovic <laurent.mihalkovic@gmail.com>:
On Jun 25, 2016, at 6:34 PM, Thorsten Seitz via swift-evolution <swift-evolution@swift.org> wrote:
Am 17.06.2016 um 19:04 schrieb Dave Abrahams <dabrahams@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@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@swift.org>:
Am 09.06.2016 um 18:49 schrieb Dave Abrahams via swift-evolution <swift-evolution@swift.org>:
on Wed Jun 08 2016, Jordan Rose <swift-evolution@swift.org> wrote:
On Jun 8, 2016, at 13:16, Dave Abrahams via swift-evolution >>>>>>>>>>> <swift-evolution@swift.org> wrote:
on Wed Jun 08 2016, Thorsten Seitz >>>>>>>>>> >>>>>>>>>>> <swift-evolution@swift.org >>>>>>>>>>> <mailto:swift-evolution@swift.org>> >>>>>>>>>>> wrote:
-Thorsten
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:
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`.
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!
Functions and tuples are structural types, although it is probably possible to make tuples syntactic sugar for a Tuple type of we get the necessary variadic generics support.
The design the variadic generics thread seems to have preliminarily settled on is based on representing variadic generics as tuples, so I imagine we would hit some issues with circularity!