[Draft] Mixins


(Niall Young) #1

What you're describing sounds _exactly_ like Traits :slight_smile:

聽聽聽http://scg.unibe.ch/research/traits

Traits could be a peer of extensions, filling a niche that isn't quite the same niche as a default implementation, but it could be consistently and safely consumed like an extension, with its own rules of consumption (flattening) - to implement any need or explicit protocol requirement that a Class, Value Type or Protocol has at compile-time.

Think of a Trait as providing a consumable set of functions. Now imagine that we're combining the collective declarations of 1..N Traits all together, consumed by a Class or Value Type (or Protocol!) in a predicable and safe way (flattening: see the first Traits white paper).

i.e. we get the same result regardless of the order of consumption, how many times any given Trait(s) were consumed, Traits dependent on other Traits etc. Predictable results, given the same input we always get the same output. This is flattening, but read the white papers for more detail.

The process of flattening itself could be a peer of the existing rules around static dispatch vs. dynamic dispatch around default-implementations/extensions vs. class overrides.

A Trait declaration could look something ~like:

聽聽聽trait TraitX (ProtocolAdherenceY): DependentTrait1, DependentTrait2 {

聽聽聽聽聽private var foo
聽聽聽聽聽private let bah { .. }

聽聽聽聽聽func fooify { .. }
聽聽聽聽聽mutating func bahify { .. }
聽聽聽聽聽private func hah { .. }

聽聽聽}

with a Trait being a closure, where _only private_ data Properties can be declared, providing 1..N function implementations. It could conform _towards_ a Protocol (partial to full conformance), and also be dependent upon other names Traits, which would be consumed in parallel as a first-class citizen with the Trait that depends on it.

Traits could be consumed by a class or value type, to provide function implementations which could be satisfying a Protocol implementation requirement, along with its own private functions and private data for its (private to Trait) local state etc. The consumption syntax I'm still unsure of, but a clear declarative "flattens Trait1, Trait2, .. , TraitN" or similar would do.

The consuming Class or Value Type would remain fully responsible for its own Protocol conformance, and if any of the consumed Trait public implementations conflict or overlap with each other, then the conflicts must be resolved explicitly by the Class or Value Type itself, where it is consumed.

The resulting "flattened" set of Traits would be input towards the Type's own compiler requirements, with the author being required to explicitly resolve any and all conflicts at compile-time. Cconsumption at run-time could be a later feature as Swift's core stabilises and specific run-time metaprogramming facilities are exposed.

Explicit conflict resolution, via a flattened 2D matrix of Trait:func identifying conflicts that need to be resolved by the consumer, also gives reliable results with no cognitive overhead as Brian's identified in resolving Mixin consumption. Plus there is no diamond-problem, as there is no inheritance. With Traits, it _must_ be resolved explicitly in code, with suitable compiler errors for malformed Types that have consumed 1..N Trait(s).

Stateful Traits suggest that as long as the data is private _to the Trait_ then we can safely ignore some of the complexity of state in Traits - it just isn't exposed as the Trait declaration itself is a closure. Dependency on state in the consumer could proxy to class/instance/value-type data via Protocol.

Swift and Protocols seem like a perfect match for "capital-T" Traits. Any thoughts on if this is suitable for a 3.0 or 4.0 Proposal?

I've recently built similar mechanisms exploring these concepts with basic metaprogramming and a common root class in a dynamic language, but as a core language feature of Swift I think it could very much complement the existing protocols and extension concepts, with 1..N re-usable implementations, and it also could help to resolves the uncertainty/rules around static vs. dynamic dispatch: static could remain the domain of extensions / default implementations; with dynamic dispatch available to Classes and Traits?

More Reading:

聽聽聽http://scg.unibe.ch/research/traits

Cheers,

路路路

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

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

--
Niall Young
niall@iinet.net.au

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

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

And agreed Thorsten!:

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.

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).

- We should extend protocols to support real multiple inheritance with
renaming

-Thorsten


(David Waite) #2

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

What you're describing sounds _exactly_ like Traits :slight_smile:

聽聽http://scg.unibe.ch/research/traits

Traits could be a peer of extensions, filling a niche that isn't quite the same niche as a default implementation, but it could be consistently and safely consumed like an extension, with its own rules of consumption (flattening) - to implement any need or explicit protocol requirement that a Class, Value Type or Protocol has at compile-time.

Think of a Trait as providing a consumable set of functions. Now imagine that we're combining the collective declarations of 1..N Traits all together, consumed by a Class or Value Type (or Protocol!) in a predicable and safe way (flattening: see the first Traits white paper).

extensions give us something trait-like, but with only one, non-opt-in implementation and usually per protocol. I鈥檇 imagine a true trait system to allow for more specific default implementations of Sequence, for example.

i.e. we get the same result regardless of the order of consumption, how many times any given Trait(s) were consumed, Traits dependent on other Traits etc. Predictable results, given the same input we always get the same output. This is flattening, but read the white papers for more detail.

The process of flattening itself could be a peer of the existing rules around static dispatch vs. dynamic dispatch around default-implementations/extensions vs. class overrides.

A Trait declaration could look something ~like:

聽聽trait TraitX (ProtocolAdherenceY): DependentTrait1, DependentTrait2 {

聽聽聽聽private var foo
聽聽聽聽private let bah { .. }

聽聽聽聽func fooify { .. }
聽聽聽聽mutating func bahify { .. }
聽聽聽聽private func hah { .. }

聽聽}

with a Trait being a closure, where _only private_ data Properties can be declared, providing 1..N function implementations. It could conform _towards_ a Protocol (partial to full conformance), and also be dependent upon other names Traits, which would be consumed in parallel as a first-class citizen with the Trait that depends on it.

Traits typically cannot declare fields/state. Is this not where you start to cross over to Mixins?

I attempted to model this (today) via delegates for behavioral dependencies on traits and self-wiring, but this creates issues with value type copying and circular references in reference types. For that reason, I didn鈥檛 feel it was appropriate to make a formal trait proposal until more definition toward behaviors (e.g. Joe Groff鈥檚 property behaviors) or macros were in the discussion.

<snip>

Stateful Traits suggest that as long as the data is private _to the Trait_ then we can safely ignore some of the complexity of state in Traits - it just isn't exposed as the Trait declaration itself is a closure. Dependency on state in the consumer could proxy to class/instance/value-type data via Protocol.

Yes, but this means that said data may be duplicated and need to be kept in sync between traits and the main type. It also means that traits cannot be used while extending an existing type to meet new protocol requirements outside a module, as that may change the size of the type itself to be different than what precompiled code expects.

-DW

路路路

On Apr 21, 2016, at 3:52 AM, Niall Young via swift-evolution <swift-evolution@swift.org> wrote:
At Tue Mar 1 19:00:21 CST 2016, Brian Pratt brian at pratt.io wrote:


(Tim Hawkins) #3

Another traits implemenation

http://php.net/manual/en/language.oop5.traits.php

路路路

On Apr 21, 2016 5:53 PM, "Niall Young via swift-evolution" < swift-evolution@swift.org> wrote:

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

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

What you're describing sounds _exactly_ like Traits :slight_smile:

聽聽聽聽聽聽聽聽http://scg.unibe.ch/research/traits

Traits could be a peer of extensions, filling a niche that isn't quite the
same niche as a default implementation, but it could be consistently and
safely consumed like an extension, with its own rules of consumption
(flattening) - to implement any need or explicit protocol requirement that
a Class, Value Type or Protocol has at compile-time.

Think of a Trait as providing a consumable set of functions. Now imagine
that we're combining the collective declarations of 1..N Traits all
together, consumed by a Class or Value Type (or Protocol!) in a predicable
and safe way (flattening: see the first Traits white paper).

i.e. we get the same result regardless of the order of consumption, how
many times any given Trait(s) were consumed, Traits dependent on other
Traits etc. Predictable results, given the same input we always get the
same output. This is flattening, but read the white papers for more detail.

The process of flattening itself could be a peer of the existing rules
around static dispatch vs. dynamic dispatch around
default-implementations/extensions vs. class overrides.

A Trait declaration could look something ~like:

聽聽聽聽聽聽聽聽trait TraitX (ProtocolAdherenceY): DependentTrait1,
DependentTrait2 {

聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽private var foo
聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽private let bah { .. }

聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽func fooify { .. }
聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽mutating func bahify { .. }
聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽private func hah { .. }

聽聽聽聽聽聽聽聽}

with a Trait being a closure, where _only private_ data Properties can be
declared, providing 1..N function implementations. It could conform
_towards_ a Protocol (partial to full conformance), and also be dependent
upon other names Traits, which would be consumed in parallel as a
first-class citizen with the Trait that depends on it.

Traits could be consumed by a class or value type, to provide function
implementations which could be satisfying a Protocol implementation
requirement, along with its own private functions and private data for its
(private to Trait) local state etc. The consumption syntax I'm still
unsure of, but a clear declarative "flattens Trait1, Trait2, .. , TraitN"
or similar would do.

The consuming Class or Value Type would remain fully responsible for its
own Protocol conformance, and if any of the consumed Trait public
implementations conflict or overlap with each other, then the conflicts
must be resolved explicitly by the Class or Value Type itself, where it is
consumed.

The resulting "flattened" set of Traits would be input towards the Type's
own compiler requirements, with the author being required to explicitly
resolve any and all conflicts at compile-time. Cconsumption at run-time
could be a later feature as Swift's core stabilises and specific run-time
metaprogramming facilities are exposed.

Explicit conflict resolution, via a flattened 2D matrix of Trait:func
identifying conflicts that need to be resolved by the consumer, also gives
reliable results with no cognitive overhead as Brian's identified in
resolving Mixin consumption. Plus there is no diamond-problem, as there is
no inheritance. With Traits, it _must_ be resolved explicitly in code,
with suitable compiler errors for malformed Types that have consumed 1..N
Trait(s).

Stateful Traits suggest that as long as the data is private _to the Trait_
then we can safely ignore some of the complexity of state in Traits - it
just isn't exposed as the Trait declaration itself is a closure.
Dependency on state in the consumer could proxy to
class/instance/value-type data via Protocol.

Swift and Protocols seem like a perfect match for "capital-T" Traits. Any
thoughts on if this is suitable for a 3.0 or 4.0 Proposal?

I've recently built similar mechanisms exploring these concepts with basic
metaprogramming and a common root class in a dynamic language, but as a
core language feature of Swift I think it could very much complement the
existing protocols and extension concepts, with 1..N re-usable
implementations, and it also could help to resolves the uncertainty/rules
around static vs. dynamic dispatch: static could remain the domain of
extensions / default implementations; with dynamic dispatch available to
Classes and Traits?

More Reading:

聽聽聽聽聽聽聽聽http://scg.unibe.ch/research/traits

Cheers,

--
Niall Young
niall@iinet.net.au

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

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

And agreed Thorsten!:

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.

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).

- We should extend protocols to support real multiple inheritance with

renaming

-Thorsten

_______________________________________________

swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Niall Young) #4

Think of a Trait as providing a consumable set of functions. Now imagine that we're combining the collective declarations of 1..N Traits all together, consumed by a Class or Value Type (or Protocol!) in a predicable and safe way (flattening: see the first Traits white paper).

extensions give us something trait-like, but with only one, non-opt-in implementation and usually per protocol. I鈥檇 imagine a true trait system to allow for more specific default implementations of Sequence, for example.

Exactly, Sequence protocol requirements could be met and provided directly by any Trait(s) consumed by the protocol as default implementation(s), or consumed by a Class or Value Type to partially or fully meet its protocol conformance requirements. Fine-grained Traits of behaviour could be consumed in many different contexts, so it's similar in some respects to AOP in that the code relating to that "aspect" or behaviour is centralised into a composable entity which any class, value type or protocol could consume and use.

with a Trait being a closure, where _only private_ data Properties can be declared, providing 1..N function implementations. It could conform _towards_ a Protocol (partial to full conformance), and also be dependent upon other names Traits, which would be consumed in parallel as a first-class citizen with the Trait that depends on it.

Traits typically cannot declare fields/state. Is this not where you start to cross over to Mixins?

The Stateful Traits papers have explored the issues with introducing state on top of behaviour, but a simple solution to avoid this complexity is to not allow state period, or keep all state private to the Trait and never visible to any Trait consumer. I wouldn't necessarily mandate that Traits for Swift should or could deal with state, they could be method-only behaviours and side-step these issues entirely, declaring their own requirements in terms of state that the consumer must provide, for example, or adherence to a protocol that the consumer must satisfy.

I attempted to model this (today) via delegates for behavioral dependencies on traits and self-wiring, but this creates issues with value type copying and circular references in reference types. For that reason, I didn鈥檛 feel it was appropriate to make a formal trait proposal until more definition toward behaviors (e.g. Joe Groff鈥檚 property behaviors) or macros were in the discussion.

I'll do some reading - was this a previous swift-evolution thread? I've not started looking at Swift's own implementation, so I haven't thought out exactly how they could be implemented, but on the surface at a syntactic and feature level it seems like a good fit for Swift and a perfect complement to protocols, enabling fine-grained code re-use with inherent predictability and safety.

<snip>

Stateful Traits suggest that as long as the data is private _to the Trait_ then we can safely ignore some of the complexity of state in Traits - it just isn't exposed as the Trait declaration itself is a closure. Dependency on state in the consumer could proxy to class/instance/value-type data via Protocol.

Yes, but this means that said data may be duplicated and need to be kept in sync between traits and the main type. It also means that traits cannot be used while extending an existing type to meet new protocol requirements outside a module, as that may change the size of the type itself to be different than what precompiled code expects.

If I understand you correctly (please elaborate if I don't :)) the data that a Trait owns would never be visible to any consumer, other Trait(s), or any other instance where that same Trait has been consumed. It wouldn't necessarily need to be stored against the consuming Type, only made visible to that specific Trait "instance" for use by its methods. Trait data wouldn't be shared with any other code, the consumer or even other "instances" of that Trait being consumed elsewhere, so I think of it more as lexically scoped private variables - similar to contextual data visible to a closure, yet no other code will be able to see or access them. It wouldn't necessarily need to be stored with the consuming Type, but I don't know if that's even feasible or desirable yet.

Exactly how Traits could be implemented under the hood for Swift - I'll do some reading and walk through the source, but others would be better placed to comment on the feasibility of this right now. I'm curious, so I'll see what I can come up with on my own.

Cheers,

路路路

On Thu, 21 Apr 2016, David Waite wrote:

--
Niall Young
niall@iinet.net.au