"Syntax for Efficient Traits" is now ready for discussion (was: Classes as Sugar is...)
Tom Van Cutsem
tomvc.be at gmail.com
Wed Sep 15 11:44:37 PDT 2010
> Yeah, I see. Though, in ES it should be quite clear: there is one
> (vertical) prototype chain; at every link of the prototype chain, can be
> several included traits/mixins (i.e. horizontal chain of mixins). Thus, the
> last included module -- overrides (shadows) the previously included one. The
> same again is made in Ruby. At last, the whole inheritance chain (vertical +
> horizontal) is linear.
> I.e. possibly the diamond problem should not be solving in case of
> mixins/traits at all. Because traits/mixins are *exactly small traits*(small additional part of functionality), which may be
> *completely independent by semantics* from the the vertical chain. And
> therefore there is no need to know -- which method to call. The answer is
> simple (in case of mixins) -- the last one included module -- overrides
> previously included module in respect of the same method name (thus, in
> Ruby, own a class's method with the same name overrides module's one).
> It's the easiest way though (Python e.g. uses linearization too to avoid
> diamonds issue). The way with warning a user about naming conflicts (in
> particular, in your implementation too) seems also good. From the other hand
> we don't warn a user if he overrides (in JS) a parent method with the same
Linearization is indeed a well-known approach to resolve the diamond, but it
doesn't adequately 'solve' the problem from a software evolution point of
view: small changes in one part of a large code base (i.e. changing the
order of two 'include' or 'extends' statements) may end up impacting a
totally different part of the code base - usually without warning. I can
recommend Alan Snyder's classic 1986 OOPSLA paper titled "Encapsulation and
Inheritance in Object-oriented Programming Languages", where he explains
this and related issues with inheritance.
> On the other hand, stateless traits have their drawbacks: you need to
> pollute a trait's public API with getter/setter methods to 'fake' state.
> I though (again, maybe in "original traits"?) that all needed state for
> a trait is provided by a class (which includes the trait). And the trait in
> contrast with a mixin do not provide (and create during execution of its
> methods) any additional object's state, creating new instance properties. Or
> you mean some additional (auxiliary) variables needed for a trait?
Yes, you are right: original traits do not define instance properties. What
I meant is that, if an original trait's provided methods _depend_ on
instance properties, then in principle the trait should _require_ a
getter/setter to manipulate those instance properties. This forces a class
that uses an original trait to provide a (usually public) getter/setter
method such that the trait can access the instance property. So actually I
was incorrect: it's not the trait's public API that is polluted, but the
class that uses trait's API (if the property was not intended to be visible
to external clients in the first place).
> The key idea behind the Traits discussed here and in that paper is that
> they are 'generative': you generate them by calling a function that returns
> new traits.
> Yeah, sort of Meta-Traits (constructors of traits).
Indeed. Although I would stick with the more straightforward term 'trait
generator'. The original Squeak Smalltalk traits implementation actually
defined the concept of a MetaTrait (its role is to traits what metaclasses
are to Smalltalk classes, but let's not go there ;-)
> This allows these traits to capture lexically visible state (usually the
> arguments of the generator function). Using this approach you can make
> stateful traits, and apart from instantiation issues (see below), there is
> no need to distinguish a class from a trait anymore.
> Yes, I see. It seems interesting approach. Since you have all needed
> (auxiliary) variables for a trait and do not pollute (as a mixin) object's
> state. I.e. the object may even not to have required methods/state for the
> trait? All this may be passed as arguments for a meta-trait?
> Ruby's modules and instance-class relationships indeed work via
> delegation and enable changes to all live instances by changing
> modules/classes, and per-instance customization. OTOH, because they chain
> together objects/classes/modules via delegation instead of flattening the
> properties, they don't provide the early conflict detection properties of
> traits. I think you can get either one or the other, but not both from 1
> would allow us to express object composition in different ways, with
> different tradeoffs.
> Yes, that exactly I was asking Mark. So the "issue" is that's not possible
> to have at the same time delegation base traits+protos and early conflict
> detection. I see. Don't know what is better. Such conflicts appear not often
> (and usually consciously are made by a programmer, i.e. "yes, I knew that
> previous mixin/trait also has such method, and I want to use the method from
> the second module. Therefore, I include first this module, and then -- the
> second one. If I wanted vice-versa, I'd included first the second module,
> and the the first one" -- that what is Ruby uses).
I agree, but I wish things were that simple. It all depends what your point
of view is: from a software maintenance point of view, explicit conflict
resolution (rather than implicit overriding) would be the better
feature. I'm not denying that there is tradeoff here: traits are a more
complex language feature than single inheritance, and don't cater to the
'live updating' feature of prototypal inheritance, but they support "safe"
multiple inheritance and early conflict detection.
> In MarkM's proposal, a "trait class" is a function that, when called,
> returns a property descriptor map. A "class" is a function that, when
> called, returns an object created from a property descriptor map. While
> technically you can "instantiate" a trait by calling a trait class function,
> you will end up with a property descriptor map, not an instance. From the
> end-user's point of view, it still only makes sense to instantiate class
> Yes, I see. And then this property descriptor (an instantiated trait) is
> merged with the original object.
> OK, Tom, thanks for your clarifications, it was useful.
> The idea of "non-traditional available for instantiation traits" sounds
> interesting. However, the issue with non-inheritable classes is have to
> avoided. I may ask any JS-programmer -- whether he needed classes in JS
> additionally to the (already available "classes" as "constructor+prototype"
> pair) prototypes, and moreover, these classes *cannot inherit* and every
> object created from a class will have *own methods* (but delegated ones)
> -- they will answer -- "No".
to prototypal inheritance, such as "mixing in" properties of one object by
copying them into other objects? Traits would provide more direct support
for these use cases.
-------------- next part --------------
An HTML attachment was scrubbed...
More information about the es-discuss