[swift-evolution] [Draft] Mixins

Brian Pratt brian at pratt.io
Tue Mar 1 19:00:21 CST 2016

I feel like the solution to the Arrow/Enemy problem that best fits with
Swift's current available tools is neither a protocol (which, as you
mentioned, doesn't get rid of the initialization/configuration of the
member data) or inheritance (which, as you mentioned, can only have one
base type) -- it's to extract a third type that handles the position and
speed of each, and let that be where the `move` functionality operates (or
the type that a `move` func operates on).

I think this sort of composition is preferable to inheritance in a lot of
ways, and Swift has some built-in tools that can augment it: a robust
protocol and extension system, a type constraint system that allows for
lots of flexibility at compile-time, etc.

Mixins (and in general, the sharing of code primarily via inheritance) tend
to create large objects with lots of responsibilities, and that tends to
bloat APIs as you need to either pick extremely specific names to avoid
collisions, or worse, keep the cognitive overhead of "shoot, what is this
method aliased to again?" in your head all the time. If something *is* both
an A and a B, it needs to act like (and speak the same language of) an A or
a B *all* of the time.

Beyond this, I think it's going to be extremely complex managing
compile-time type constraints with renames in place. Let's say I have a
class C that inherits from bases A and B, which implement protocol P and Q
respectively, and there's a naming collision. Functions that expect Ps or
Qs will have to know about the renaming of conflicts from the combination
of A+B? Unless I'm missing something, it feels like this complexity would
continue to spread out to all sorts of collaborators, when the current
system isolates it much more effectively.

I think protocols and protocol extensions (mixed with lots of composition)
is a better scenario than abstract classes or multiple inheritance, and
therefore, I'm still a -1 on mixins in Swift (strictly on principle; this
proposal actually argues the case very well).

 - Brian

On Tue, Mar 1, 2016 at 5:25 PM, Howard Lovatt via swift-evolution <
swift-evolution at swift.org> wrote:

> @Thorsten,
> I don't get why you voted -1, it seems like all you want to add is the
> ability to rename in case of classes as an alternative to redefine which
> the proposal currently provides. Why not vote +1 and say expand to include
> renaming?
> Your second point that all you need is a class is also true. The mixing as
> proposed could subsume structs and protocols. Though not classes because
> they are passed by reference wagers structs are passed by copy. But again
> that could be an expansion: expand structs so that they behave like
> mixing/traits and allow classes to inherit from structs.
>  -- Howard.
>   -- Howard.
> On 2 March 2016 at 09:09, Thorsten Seitz via swift-evolution <
> swift-evolution at swift.org> wrote:
>> > Am 29.02.2016 um 19:51 schrieb Антон Жилин via swift-evolution <
>> swift-evolution at swift.org>:
>> >
>> > I'm prepairing to create a pull request, so I moved the proposal from
>> Gist to my fork of swift-evolution.
>> > Link to the proposal new and hopefully final home:
>> >
>> https://github.com/Anton3/swift-evolution/blob/mixins/proposals/NNNN-mixins.md
>> I'm -1 to the proposal as written and will try to explain that.
>> Regarding mixin inheritance:
>> > Problem of members with same signatures inherited from two different
>> places results in compile error.
>> This is unfortunate as there exists a simple solution for this problem in
>> statically typed languages (more on that later).
>> > Diamond problem is solved by keeping only one copy of mixins mixed-in
>> in the final struct/class.
>> > It works as if bodies of all mixins indirectly mixed-in into D were
>> merged directly into D
>> That is not a good solution but just a workaround. It probably has
>> origins in dynamically typed languages where a proper solution is not
>> possible (see below).
>> Unfortunately the current discussions about Mixins, abstract classes, POP
>> vs. OOP suffer from having forgotten achievements of the past which results
>> in creating differences where none should be.
>> Let's start with protocol oriented programming (POP). This is a term
>> coined by Dave Abrahams in his great talk at WWDC 2015. The only problem
>> IMO is that POP is nothing new. It is just good old interface oriented
>> programming as has been possible in Java for a long time (the new thing
>> Swift brought to the table is first class value semantics). Actually if you
>> look closely at Dave's talk is not about POP vs. OOP it is about Multiple
>> Inheritance Done Right (TM) vs. single inheritance (with a smattering of
>> value objects).
>> And protocols in Swift have just the same motivation like interfaces in
>> Java (for the moment disregarding value types): they are classes without
>> state because people thought that the dreaded diamond problem is about
>> state and they thought that if dropping state would make multiple
>> inheritance possible.
>> That's true to some extent. But actually the diamond problem is not about
>> state. State is only where it surfaces more easily.
>> Actually the diamond problem is about inheriting a member with the same
>> name (or signature) twice. And that is relevant for protocols as well.
>> And the beauty is that the solution is the same for state as for members.
>> So, what is the problem? It is as follows:
>> GENERAL DIAMOND PROBLEM: When a subclass inherits a member with the same
>> name twice we have to know whether both inherited members have the same
>> semantics.
>> (A) If both have the same semantics then we only want to use one
>> implementation slot, so we either choose a preferred one from the parents
>> or we implement the method in the subclass and are free to call one or both
>> super implementations or replace their logic with something new. What we do
>> is defined in the subclass.
>> In case of storage we don't have to do anything, the state is merged
>> automatically (i.e. there will be only one storage slot).
>> (B) If both have different semantics we want to differentiate between
>> both implementations or storage slots. For that we have to rename one or
>> both in the subclass so they don't clash anymore. When using an instance of
>> the subclass with a static type of one of its parents the original name is
>> used of course in the type i.e. at the call site but the correct renamed
>> one is used internally. Which one is clear as that is given by the static
>> type used.
>> If a (possibly renamed) member has been abstract it remains abstract
>> unless implemented in the subclass. Of course I can still override a member
>> in the subclass even if it was renamed.
>> The classical diamond problem will in most cases fall into category (A),
>> i.e. both inherited members have the same semantics and therefore only one
>> storage slot ist used. In some cases we might want to duplicate the storage
>> slot but this implies different semantics, so (B) applies and renaming
>> solves this quite nicely, thereby making the different semantics clear by
>> the now different names.
>> Addendum: in the diamond case we have to `select` one of both members to
>> be used when accessed through the top type.
>> Bertrand Meyer's Eiffel demonstrates this solution quite nicely and
>> successfully and that's why there are no interfaces in Eiffel, just
>> classes. Because interfaces are not needed if classes use multiple
>> inheritance. Just add abstract methods and you are done. No need to
>> introduce new concepts.
>> See https://www.eiffel.com/values/multiple-inheritance/
>> and
>> https://docs.eiffel.com/book/method/et-inheritance#Multiple_inheritance_and_renaming
>> (for the details)
>> EXAMPLE 1: (from the Eiffel webpages, renaming syntax to be bikeshedded)
>> class Array {
>>         var count: Int
>> }
>> class List {
>>         var count: Int
>> }
>> class ArrayList : Array renaming count to capacity, List renaming count
>> to size { ... }
>> // ArrayList exposes the renamed members only under their new name
>> let arrayList: ArrayList = ...
>> arrayList.size // ok, answers the count as inherited from List
>> arrayList.capacity // ok, answers the count as inherited from Array
>> arrayList.count // error: undefined `count`
>> // The original names must still be accessible from the parent types,
>> e.g. Array
>> let array: Array = arrayList
>> array.count // ok, answers the count as inherited from Array (=
>> arrayList.capacity)
>> array.capacity // error: undefined `capacity`
>> EXAMPLE 2: (from the Eiffel webpages, renaming syntax to be bikeshedded)
>> class UniversityPerson {
>>         var computerAccount: Account
>> }
>> class Teacher : UniversityPerson { ... }
>> class Student : UniversityPerson { ... }
>> class TeachingAssistant :
>>         Teacher renaming computerAccount to facultyAccount select
>> facultyAccount,
>>         Student renaming computerAccount to studentAccount {
>>         ...
>> }
>> let assistant: TeachingAssistant = ...
>> assistant.facultyAccount // ok
>> assistant.studentAccount // ok
>> assistant.computerAccount // error: undefined `computerAccount`
>> let teacher: Teacher = assistant
>> teacher.computerAccount // ok (= assistant.facultyAccount)
>> let student: Student = assistant
>> student.computerAccount // ok (= assistant.studentAccount)
>> let universityPerson: UniversityPerson = assistant
>> universityPerson.computerAccount // ok (= assistant.facultyAccount, due
>> to `select`)
>> Dynamically typed languages like Ruby cannot use this solution because it
>> is not possible to map a call being made by using a parent type (Array in
>> the example) to the renamed member implementation (Array.count ->
>> ArrayList.capacity) as there are no types and it would not be clear whether
>> array.count would need to call `capacity` or `size`. So renaming is not a
>> possibility in dynamically typed languages which is why they resort to
>> workarounds.
>> So in essence interfaces or protocols aren't needed at all if multiple
>> inheritance is done right.
>> Citing from the Eiffel web pages: "Some O-O languages support only the
>> two extremes: fully effective classes, and fully deferred "interfaces", but
>> not classes with a mix of effective and deferred features. This is an
>> unacceptable limitation, negating the object-oriented method's support for
>> a seamless, continuous spectrum from the most abstract to the most
>> concrete."
>> Now we come back to value types in Swift (which are a great thing!). Why
>> do we need protocols for value objects or rather why is inheritance not
>> allowed for them (because if it was and if multiple inheritance was done
>> right no protocols would be needed)?
>> AFAIU and I might be very wrong here, inheritance is not allowed for
>> value types because we want to embed value types within other value types
>> (e.g. arrays but other structs as well) without having to use references.
>> But if a value subtype would have added some state an object of that
>> subtype would require more memory than an object of its parent type which
>> is not possible.
>> The solution is to forbid inheriting state. And what is commonly used for
>> inheritance without state? Right, interfaces, a.k.a. protocols.
>> Another solution would have been to just forbid inheriting from a class
>> with state. But that ship has sailed a long time ago, I fear, and protocols
>> add another variety of generics into the language feature mix which offers
>> some nice advantages (while having some disadvantages as well, but that is
>> another topic and there are solutions for that as well).
>> TL;DR
>> It is unfortunate and IMO just for historical reasons that there is a
>> dichotomy between protocols and classes at all instead of having just
>> classes with multiple inheritance done right (and abstract methods).
>> What does that mean for the proposals currently being discussed (Mixins,
>> abstract classes)?
>> - I think we should NOT add mixins with less than full multiple
>> inheritance.
>> - We should extend protocols to support real multiple inheritance with
>> renaming
>> - An open question is whether we should extend protocols to support state
>> and what to do with classes in that case (maybe reduce them to
>> implementation inheritance and having types defined solely by protocols;
>> this would enable classes to use their own namespace different from the
>> namespace of types and get rid of the problem of Collection vs.
>> CollectionImpl or CollectionType vs. Collection).
>> In an ideal world we would just have classes with multiple inheritance
>> and abstract methods and would be able to mark classes as value types which
>> would make them have value semantics like current Swift structs. If
>> subclasses of value types introduce additional state they do not conform to
>> their parent but start a new virtual root in the inheritance tree, i.e.
>> abstract class Vector<N: Number> {
>>         func +(lhs: Self, rhs: Self) -> Self
>>         func *(lhs: N, rhs: Self) -> Self
>> }
>> value class Vec2d<N: Number> : Vector<N> {
>>         var x: N
>>         var y: N
>> }
>> value class Vec3d<N: Number> : Vec2d<N> {
>>         var z: N
>> }
>> var v2: Vec2d<Float> = ...
>> var v3: Vec3d<Float> = ...
>> var v: Vector<Float>
>> v = v2 // ok
>> v = v3 // ok
>> v2 = v3 // type error: Vec3d is not a subtype of Vec2d
>> For generics I would use both variants that Swift currently supports,
>> i.e. explicit type parameters and associated types, freely combinable.
>> Just dreaming... sigh.
>> -Thorsten
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution at swift.org
>> https://lists.swift.org/mailman/listinfo/swift-evolution
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160301/4872e5da/attachment.html>

More information about the swift-evolution mailing list