"Syntax for Efficient Traits" is now ready for discussion (was: Classes as Sugar is...)
Dmitry A. Soshnikov
dmitry.soshnikov at gmail.com
Thu Sep 16 04:45:20 PDT 2010
On 15.09.2010 22:44, Tom Van Cutsem wrote:
> 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 name.
> 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.
Yeah, but as I mentioned usually a user is aware about which modules he
includes and know which consequences (just a shadowing) he will have in
case of the same method names. In case if the user doesn't know with
which modules he deals, yeah, it's good to inform him about that "be
careful, the method of this trait shadows the previous one". By the way,
as I also mentioned, it's not a big problem to have it (delegation based
mixins with liniarization and naming conflicts avoiding) even
implementing on ES3 -- http://gist.github.com/575982. There nothing
prevents me to add the check at the beginning of the "mixing" function,
whether any method from "module" is (in the inheritance + mixins chain)
already in the "to" object. Though, this implementation is mostly about
academic curiosity and has some lacks, however if to use new Proxy
objects or even internal property [[traits]] to handle mixed traits
chain on [[Get]], then it's easy possible to have the bundle "delegation
based traits + naming conflicts avoiding" mechanism. At least at mixing
stage (to catch the case when a new method is added to a trait, after
the trait is already mixed, it's needed to have overridden e.g.
[[DefineOwnProperty]] for traits).
> 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.
Oh, thanks, I will (as also your paper about generative traits).
>> 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 language
>> traits 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.
Yeah, I think so.
>> 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".
> alternatives 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.
With the traits idea everything is clear for me now, it seems a good
replacement for the current Prototype.js's "Object.extend" (which I
again remind is borrowed from Ruby and there has a delegation-based
nature). So here there are no questions.
But I said about useless /classes/ (where "useless" means: (1)
non-inheritable and (2) again -- defining own properties but not
delegation based). Though, the question should go to MarkM I guess. I've
already heard some meanings "what? they provining classes? for what?".
The answer is simple "for what?" -- just for a convenience sugar for
/chaining prototypes/. Again, from this viewpoint, ECMAScript already
has "classes" as a pair of "a Constructor function" + "a prototype
chain". Because, Python's /"first-class" dynamic delegation based
classes/ do not differ (much) from this ECMAScript's model. In Python
classes are really just a /sugar/ -- a convenient way /just to chain
these "prototypes"/. Exactly this sugar has e.g. again CoffeeScript --
http://jashkenas.github.com/coffee-script/#classes -- a nicely made
wrapper sugar to chain the prototypes.
What instead we have in "classes-as-sugars" proposal? It's not a sugar
for chaining prototypes, but just some "high-integrated factory" which
may just /clone/ objects by specified pattern. Neither more, nor less.
It's cannot even inherit reusing the code. As a consequence from
non-inheritable nature, it defines everything (properties/methods) as
/own/. That exactly I'm asking. And I really think, that when
JS-programmers will see these classes, they just say: "No, thanks. What
for? For this private state? I can made it the same even in ES3 and
moreover -- reusing private methods".
So "classes-as-sugars" in the view which are they now aren't seemed me
the sugar. Because they brings completely different ideology to already
available in ES, combining delegation (to prototypes) based inheritance,
and some non-inheritable "clone factories".
And with traits everything's clear. Thanks ;) It's a good idea.
-------------- next part --------------
An HTML attachment was scrubbed...
More information about the es-discuss