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?


Correct.


    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
    feature. Javascript already has prototype delegation. Adding
    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
    functions.


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


While I see your point, don't Javascript programmers also use 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.

Dmitry.


Cheers,
Tom

_______________________________________________
es-discuss mailing list
[email protected]
https://mail.mozilla.org/listinfo/es-discuss

Reply via email to