[Draft] Mixins

On "traits vs mixins",

I tend to agree with Patrick Gili that probably the most important difference between traits and mixins is that mixins define state. This article agrees: http://matthijshollemans.com/2015/07/22/mixins-and-traits-in-swift-2/

On initializers,

Initializers really only make sense on fully defined types. That's why I disallowed implicit initializer inheritance. But I believe that "helpers", which know how to initialize their part of state, can be useful.

I think we should allow for a mixin to be fully responsible for its state (which might be private). Therefore initialization should be possible.

On mixin conflicts resolving,

I agree that this question should be solved not in "future directions", but within the proposal itself. "Merge members" and "keep copies for both super mixins" are equal alternate options.

We need to decide on syntax. I also don't fully understand how it should work, question below.

A special thanks ot Thorsten Seitz for opening my eyes on problems in Swift type system. I agree with "dream" part completely. But I'm afraid, that already can't be changed.

Thanks! I'm glad that I'm not alone :-)

New set of questions:

  1. Can a mixin contain uninitialized state, unlike fully defined types? Example:

mixin A { var x: Int }

struct B: A { init(x: Int) { self.x = x } }

If such a mixin would be marked as "abstract" that should be no problem.

  1. What should be syntax for conflict resolution? I will use the following in this post:

var x = A.x

func f() = A.f

While that syntax looks very concise "var y = A.x" does not convey very good that y replaces x in the new type. But let's stay with that syntax for the moment as we can bikeshed that later.

The intent here should be to use existing Swift constructs.

  1. If we decide to keep two copies in a conflict, do we need to rename all members, or can we rename selectively? Example:

Each conflict has to be solved by itself, independently from all other conflicts, but all conflicts have to be solved one way or the other.

mixin A {

var x: Int = 0

func show() { print(x) }
}

mixin B {

var x: Int = 1

func show() { print(x) }
}

struct S: A, B {

var y = A.x

var z = B.x

// nothing touching show()

}
let s = S()

s.show() //=> 0 or 1?

show() is in conflict which has to be solved. Either by (a) renaming one or both to get two copies or by (b) selecting one implementation.

Example using choice (b):

struct S: A, B {

var y = A.x

var z = B.x

func show() = A.show // selecting implementation of A

}

let s = S()

s.show() //=> 0, because the implementation of A was chosen and A.show uses A.x

If I had chosen B.show in the definition of struct S, the result would have been 1.

This example could also be formulated for problem A -> (B,C) -> D

  1. Should we allow for arbitrary renaming of members in subtypes, not only in conflicts?

Eiffel does so which can be nice, e.g.

struct Stack : List {

func pop() = List.removeFirst()

func peek() = List.first()

}

This is not that important, though, and can easily be added later. The semantics for renaming stay completely unchanged.

  1. Should we allow conflicting inherited members until usage or throw an error immediately?

mixin A { var x: Int = 0 }

mixin B { var x: Int = 1 }

struct C: A, B { func show() { print(A.x); print(B.x) } }

Actually the error is in the definition of struct C and should be flagged there: "x is a conflicting member in C, either rename x or select one of A.x or B.x"

let c = C() // error??

C().show() //=> 01

C().x // error: "x is a conflicting member in C, cannot be called here"

C().A.x // syntax error

These are all too late.

-Thorsten

···

Am 02. März 2016 um 10:55 schrieb Антон Жилин antonyzhilin@gmail.com:

2016-03-02 8:37 GMT+03:00 Thorsten Seitz tseitz42@icloud.com:

Am 02.03.2016 um 02:00 schrieb Brian Pratt brian@pratt.io:

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.

Yes, that is exactly the point of Eiffel-style-multiple inheritance.

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?

No, functions that expect Ps or Qs will talk the language of P and Q. They don't have to know about renaming (that's the whole point which is only possible in statically typed languages).
Functions that expect C will have to know about renaming. They will talk the language of C.

-Thorsten