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