[Draft] Mixins


(Anton Zhilin) #1

Hi Niall,

This feature has been previously discussed. Search for Mixins on
swift-evolution archives.

I believe it would help to read previous version of the proposal:
https://github.com/Anton3/swift-evolution/blob/mixins/proposals/NNNN-mixins.md

This feature has been delayed to Swift 3 by Chris Lattner (at least, he
told about it).

- Anton


(Anton Zhilin) #2

Sorry, it has been delayed *from* Swift 3 to future versions.

···

2016-04-22 10:22 GMT+03:00 Антон Жилин <antonyzhilin@gmail.com>:

Hi Niall,

This feature has been previously discussed. Search for Mixins on
swift-evolution archives.

I believe it would help to read previous version of the proposal:

https://github.com/Anton3/swift-evolution/blob/mixins/proposals/NNNN-mixins.md

This feature has been delayed to Swift 3 by Chris Lattner (at least, he
told about it).

- Anton


(Anton Zhilin) #3

Yes, this is my proposal, and of course, I'm interested :slight_smile:

I haven't read into the mentioned papers, but symmetric sum, asymmetric
sum, alias, exclusion and derived operations form a solid basis for
conflict resolution.
Mixins don't have all that tools and try to solve conflicts by being less
strict at composition.
I agree that Swift, as a compiled language, would benefit from the former
more.

In traits, I like how requirements and definitions are separated using
requirement of protocol conformance.
It follows Uniform Access Principle: noone can require conformance to a
trait.
Are there other benefits?

I believe there are situations where non-privateness of fields would help,
see my two examples.
This can be overridden by adding a "proxy" computed property. But why is
privateness needed?

Do traits have initializers? How do they work in diamond pattern?

Example: A -> (B, C) -> D
B.init calls A.super.init(1)
C.init calls A.super.init(2)
D.init calls B.super.init() and C.super.init()

Traits are flattened by default. Does it mean that A will be initialized
twice? in what state will A be?

- Anton

···

2016-04-27 2:50 GMT+03:00 Niall Young <niall@iinet.net.au>:

On Fri, 22 Apr 2016, Антон Жилин wrote:

This feature has been previously discussed. Search for Mixins on

swift-evolution archives.

I believe it would help to read previous version of the proposal:
https://github.com/Anton3/swift-evolution/blob/mixins/proposals/NNNN-mixins.md

Cheers, already read through the thread - I'm more focused on implementing
Traits, which I think is very similar but slightly different to what's been
described in the thread to date.

Is this your proposal? I'd love to give you some feedback, throw ideas
back and forth if you're interested? If we can evolve the concept and
implementation by Swift 4 that gives me enough time to brush up on C++ and
start delving into Swift internals to see how best it could be done.

Cheers,

--
Niall Young
niall@iinet.net.au


(Howard Lovatt) #4

I would think that:

Example: A -> (B, C) -> D
B.init calls A.super.init(1)
C.init calls A.super.init(2)
D.init calls B.super.init() and C.super.init()

is an error because there is one A and you have inited it twice. Just like
calling super twice is an error currently.

···

On Wednesday, 27 April 2016, Антон Жилин <swift-evolution@swift.org> wrote:

Yes, this is my proposal, and of course, I'm interested :slight_smile:

I haven't read into the mentioned papers, but symmetric sum, asymmetric
sum, alias, exclusion and derived operations form a solid basis for
conflict resolution.
Mixins don't have all that tools and try to solve conflicts by being less
strict at composition.
I agree that Swift, as a compiled language, would benefit from the former
more.

In traits, I like how requirements and definitions are separated using
requirement of protocol conformance.
It follows Uniform Access Principle: noone can require conformance to a
trait.
Are there other benefits?

I believe there are situations where non-privateness of fields would help,
see my two examples.
This can be overridden by adding a "proxy" computed property. But why is
privateness needed?

Do traits have initializers? How do they work in diamond pattern?

Example: A -> (B, C) -> D
B.init calls A.super.init(1)
C.init calls A.super.init(2)
D.init calls B.super.init() and C.super.init()

Traits are flattened by default. Does it mean that A will be initialized
twice? in what state will A be?

- Anton

2016-04-27 2:50 GMT+03:00 Niall Young <niall@iinet.net.au
<javascript:_e(%7B%7D,'cvml','niall@iinet.net.au');>>:

On Fri, 22 Apr 2016, Антон Жилин wrote:

This feature has been previously discussed. Search for Mixins on

swift-evolution archives.

I believe it would help to read previous version of the proposal:
https://github.com/Anton3/swift-evolution/blob/mixins/proposals/NNNN-mixins.md

Cheers, already read through the thread - I'm more focused on
implementing Traits, which I think is very similar but slightly different
to what's been described in the thread to date.

Is this your proposal? I'd love to give you some feedback, throw ideas
back and forth if you're interested? If we can evolve the concept and
implementation by Swift 4 that gives me enough time to brush up on C++ and
start delving into Swift internals to see how best it could be done.

Cheers,

--
Niall Young
niall@iinet.net.au <javascript:_e(%7B%7D,'cvml','niall@iinet.net.au');>

--
-- Howard.


(Niall Young) #5

Yes, this is my proposal, and of course, I'm interested :slight_smile:

Cool, given it's postponed until Swift 4+ I thought it best to keep this off-list but happy to keep going here if others don't find it distracting.

I've drafted a proposal focusing on the strict definition of Traits. I'll give it another few iterations so I can flesh out where they could fit in the language relative to extensions etc. Happy to share it here, or keep it off-list for now until we can dive into Swift's internals and come up with some ideas on how best to implement this.

I haven't read into the mentioned papers, but symmetric sum, asymmetric sum, alias, exclusion
and derived operations form a solid basis for conflict resolution.

Let's keep it very simple to begin with. e.g. 3 Traits each implement one or more functions:

           function1 function2 function3 function4
TraitA X X X
TraitB X X
TraitC X

for each column where there are >1 implementations, the conflict must be resolved explicitly by choosing which implementation will be used. This is roughly how flattening works. We "flatten" them into 1 cohesive set of methods, with no conflicts, e.g. consumption syntax could look ~like

   class Foo : ProtocolA {
     flattens TraitA, TraitB, TraitC { exclude TraitA.function1 TraitC.function2 }
   }

or they could be re-used to provide protocol default implementations:

   protocol ProtocolA {
     flattens TraitA, TraitB, TraitC { exclude TraitB.function1 TraitA.function2 }
   }

that also helps to resolve the dilemma of having to conform to a full protocol when you only use a couple of specific default implementations - you pick and choose from the underlying implementations, and/or conform to a more fine-grained sub-protocol. I think Traits can help make extensions, protocols and default implementations more powerful, concise and re-usable. They seem to complement each other well.

Note that the Trait doesn't target a named Class or Protocol like extensions do, the onus is reversed so that the consuming class/struct/enumeration/protocol elects to consume that implementation(s). This means they are more fine-grained and re-usable than default implementations, and in reality I think default implementations could be provided by a protocol *or* a Trait, or both (say the Class conforms to the Protocol, but chooses to override a default implementation with an alternative from a Trait).

Renaming functions, aliasing, keeping all implementations available for the final function to call ... I'd leave all of this complexity until later.

State as well, that should remain a class/struct/enumeration responsibility. Stateful Traits could be attempted later, Trait functions could benefit from some shared state amongst themselves, but that state should not be visible outside of that "instance" of Trait - the same Trait consumed elsewhere would have its own state, and no consumer could access this state. This also means that there are no Type size issues as we're not introducing additional stored properties.

I believe there are situations where non-privateness of fields would help, see my two examples.
This can be overridden by adding a "proxy" computed property. But why is privateness needed?

Do traits have initializers? How do they work in diamond pattern?

Example: A -> (B, C) -> D
B.init calls A.super.init(1)
C.init calls A.super.init(2)
D.init calls B.super.init() and C.super.init()

Traits are flattened by default. Does it mean that A will be initialized twice? in what state
will A be?

Traits cannot be instantiated, they are not inherited, and they do not have state. So multiple inheritance and the diamond problem are not applicable here. Swift's existing rules around single-inheritance remain as-is.

Computed properties I can see a valid argument for supporting, but not stored properties. Initialisation is the responsibility of the class/struct/enumeration, not the Trait. Traits are not a solution to this problem.

The white paper is a really good read, highly recommend it: http://scg.unibe.ch/archive/papers/Scha02bTraits.pdf

···

On Wed, 27 Apr 2016, Антон Жилин wrote:

--
Niall Young
niall@iinet.net.au

2016-04-27 2:50 GMT+03:00 Niall Young <niall@iinet.net.au>:
      On Fri, 22 Apr 2016, Антон Жилин wrote:

            This feature has been previously discussed. Search for Mixins on
            swift-evolution archives.

            I believe it would help to read previous version of the proposal:
            https://github.com/Anton3/swift-evolution/blob/mixins/proposals/NNNN-mixins.md

      Cheers, already read through the thread - I'm more focused on implementing Traits,
      which I think is very similar but slightly different to what's been described in the
      thread to date.

      Is this your proposal? I'd love to give you some feedback, throw ideas back and
      forth if you're interested? If we can evolve the concept and implementation by
      Swift 4 that gives me enough time to brush up on C++ and start delving into Swift
      internals to see how best it could be done.

      Cheers,

      --
      Niall Young
      niall@iinet.net.au