Property vs Behavior inheritance
[[Prototype]] -----> [[Prototype]] ------> [[Prototype]] -------> null
myProps | parentProps grandProps | | [[Prototype]] ----+ myProps
But once we use these classes to create instances, those instances will very likely have data. Thus they will no longer be effective as 'classes'.
True this phenomenon often shows up in pre-ES5 code, as an anti-pattern: Sub.prototype = new Super();
In real JS code there is no way to tell if an object is intended to be a class. Objects that implement useful behavior seem like great candidates for "prototypical inheritance". This burns every JS developer at one time or another. And probably much more than most will admit.
But do you ever extend myProps? You have to be acutely aware of what is going on, anyway, otherwise you will wire up things incorrectly.
Classless solutions need a compelling solution to this issue, that is, an operation on a list of vanilla objects that yields a class-like object. Any such operation will fail in some cases, just like class-based solutions fail sometimes (when you really want to share data across classes). But a solution is needed for the 99% of the time when you don't want data sharing.
A naming convention is not enough? Right now that works reasonably well for “functions versus constructors”. With object exemplars, you have:
var jane = new Person("Jane");
var Employee = Person <| { ... };
I don’t think one would make the mistake of extending jane. Ironically, a class-based mindset works well here and does not lead you astray.
Le 15/12/2011 18:17, John J Barton a écrit :
(I know the language experts know all this, I'm spelling it out for fellow amateur 'classless' fans)
After using (and liking) selfish I understand why prototypical inheritance via simple references is not an effective alternative to classes.
The new Foo/Foo.prototype JavaScript idiom sets up a lookup table like this:
[[Prototype]] -----> [[Prototype]] ------> [[Prototype]] -------> null myProps | parentProps grandProps | | [[Prototype]] ----+ myProps
(...)
The reason is simple: 99% of the time you want base class functions to be inherited but not base class data values. In setting up "objects-that-will-be-classes" we can consciously avoid data values. I did not say this above, but most readers will imagine that all of the objects in the diagram above have only functions. That is essential what we mean when we thing of these objects as classes: no data properties.
WebIDL is taking a different direction [1]: the ECMAScript representation of WebIDL attributes (like 'cookie' property of the document object) is placed as an accessor property on the prototype related to the given interface (Object.getOwnPropertyDescriptor(HTMLDocument.prototype, 'cookie') is an accessor property descriptor)
Consequently, objects used in the prototype layer have other things than just functions. (as far as I know, the only browser that currently conforms decently to this model is IE9+)
(...)
JS projects address this issue by conventions and they can be successful. One in particular is 'selfish': the split between behavior and data is created by declaring behavior but implementing all data values imperatively in initialize(). It is convention only.
True. What WebIDL does is another convention.
Class-based languages solve this by building the 'behavior' or function layer differently from the 'data' layer. The functions are placed in vtable data structures referencing base class vtables; data values are concatenated across all classes as properties for the instance.
Classless solutions need a compelling solution to this issue, that is, an operation on a list of vanilla objects that yields a class-like object. Any such operation will fail in some cases, just like class-based solutions fail sometimes (when you really want to share data across classes). But a solution is needed for the 99% of the time when you don't want data sharing.
I think that traits will be another form of convention that will avoid name collision (or assist the developer to deal with them explicitely when they occur)
I agree with your intention, but not your conclusion. The language provides a very generic interface when it comes to composition. Between selfish, what WebIDL does and traits, we have at least 3 different relevant approaches to composition, why should the language pick one over all those possible?
ECMAScript 5, by providing Object.{create|defineProperty|getPrototypeOf}, other introspection methodsand and accessor properties (which were in some browsers but not in ES3) helped advertise how the language works by showing the fundamental constructs of the language to the programmer. It's been implemented only recently (relatively to the history of the language and that we've been stuck on ES3 for cross-browser compatibility concerns) Some other fundamental constructs are on their way (proxies, reflection API, WeakMaps, maps, sets, proto operator (x2), private names) and these will certainly influence too how we think of and implement object composition.
I'm not sure the language should decide now what approach to composition it should favor... or if it should favor one (or more) at all! I think we need at least (!) a year or two with all these features implemented, running and used to see the big picture. I agree to discuss the question but hope that no decision will be made now. It sounds premature.
David
[1] www.w3.org/TR/WebIDL/#es-attributes [2] www.w3.org/TR/html5/dom.html#htmldocument
On Thu, Dec 15, 2011 at 9:51 AM, Axel Rauschmayer <axel at rauschma.de> wrote:
A naming convention is not enough? Right now that works reasonably well for “functions versus constructors”.
My assessment differs: "The capital-letter constructor solution works well for a single level of inheritance in simple systems"
With object exemplars, you have:
var jane = new Person("Jane"); var Employee = Person <| { ... };
(Since I guess only long time readers of this newsgroup could understand what you write above, let me give a capsule summary: Employee becomes an object with a .prototype property referring to an object with properties of the RHS and a [[Prototype]] of the LHS. Employee.prototype to inherits from Person.prototype and adds properties in {}.)
What constructor is invoked if I write: var jack = new Employee(...); ?
Let's just make your example slightly more real. There is nothing to help us avoid this kind of pothole: var Employee = Person <| { hoursPerWeek: 40 }; var Contractor = Employee <| { ... }; var PartTimer = Employee <| {...}; The failure mode derives from using object literals for both objects and classes. Experience, review, or debugging have to save us. (I guess we can't require the operands of <| to be capitalized and the RHS to have only functions?). I don't think we make this error in class-based languages.
The <| operation solves the same problem that seflish solves: behavior inheritance; they both use object literals (optional in selfish); neither address the data-property specification problem nor the data-property initialization problem.
I don’t think one would make the mistake of extending jane. Ironically, a class-based mindset works well here and does not lead you astray.
Indeed, and perhaps that is my point. Does a classless solution make sense if in fact you have to rigorously apply extra-lingual class-based reasoning to succeed?
To be clear, I'm a JS fan; I don't want it to become Java. Nevertheless our current inheritance system is pretty silly.
jjb
With object exemplars, you have:
var jane = new Person("Jane"); var Employee = Person <| { ... };
(Since I guess only long time readers of this newsgroup could understand what you write above, let me give a capsule summary: Employee becomes an object with a .prototype property referring to an object with properties of the RHS and a [[Prototype]] of the LHS. Employee.prototype to inherits from Person.prototype and adds properties in {}.)
No, Employee becomes an object whose [[Prototype]] is Person (see [1]).
What constructor is invoked if I write: var jack = new Employee(...);
This is roughly equivalent to:
var jack = Object.create(Employee);
if (Employee.hasOwnProperty("constructor")) {
jack.constructor(...);
}
Let's just make your example slightly more real. There is nothing to help us avoid this kind of pothole: var Employee = Person <| { hoursPerWeek: 40 }; var Contractor = Employee <| { ... }; var PartTimer = Employee <| {...}; The failure mode derives from using object literals for both objects and classes. Experience, review, or debugging have to save us. (I guess we can't require the operands of <| to be capitalized and the RHS to have only functions?). I don't think we make this error in class-based languages.
How you work with object exemplars is almost like with classes: You have to be aware which objects are exemplars and which ones are instances. A useful sanity check could be to require an exemplar to have a constructor() method.
Note that there are always two steps involved in subtyping:
- Extend the shared properties.
- Extend the instance properties.
In Self, you have an instance object O pointing to a shared object S. To instantiate, you clone O (shallowly). To subtype, you create a new instance object O' that points to O and a new shared object that points to S. When you clone O’, you also clone O, so it’s a different kind of reference than the one between O and S.
With object exemplars, #1 is performed by making the super-exemplar the prototype of the sub-exemplar, #2 is performed by implementing constructor() and invoking super.constructor().
The <| operation solves the same problem that seflish solves: behavior inheritance; they both use object literals (optional in selfish); neither address the data-property specification problem nor the data-property initialization problem.
Yes, you have to know that instance properties are always added in the constructor. But that rule is the same with most class declaration proposals that exist. I don’t find that rule problematic, it’s just not how things are commonly done in mainstream languages – which tend to have a more declarative syntax for instance members.
Indeed, and perhaps that is my point. Does a classless solution make sense if in fact you have to rigorously apply extra-lingual class-based reasoning to succeed?
Class declarations can do some interesting things such as make sure that a prototype only contains methods. But even with class declarations, the core point of object exemplars still holds: JavaScript inheritance would be simpler if a class declaration desugared to a prototype (instead of a constructor). [I am ignoring backward compatibility which is a big thing to ignore.] Sect. 3 of [1] argues that point and gives several code examples.
Le 15/12/2011 23:38, John J Barton a écrit :
On Thu, Dec 15, 2011 at 10:58 AM, David Bruant <bruant.d at gmail.com <mailto:bruant.d at gmail.com>> wrote:
Classless solutions need a compelling solution to this issue, that is, an operation on a list of vanilla objects that yields a class-like object. Any such operation will fail in some cases, just like class-based solutions fail sometimes (when you really want to share data across classes). But a solution is needed for the 99% of the time when you don't want data sharing.
... I agree with your intention, but not your conclusion. The language provides a very generic interface when it comes to composition. Between selfish, what WebIDL does and traits, we have at least 3 different relevant approaches to composition, why should the language pick one over all those possible?
Because practical experience shows that one approach has applies to many more cases than other approaches.
I think that practical experience is biased. IE6/7/8 do not have any mecanism to define accessors. Lack of private properties forced to either put all properties as public for them to be used by prototype method or to duplicate methods at the base level. I personally do the latter, because I care about encapsulation. My practical experience in how I organise my objects is biaised by something missing in the language.
However, I did not advocate for one approach to composition. Rather I am seeking a way to deal with data-properties in a classless composition. The operator |> solves one part of the inheritance problem, the part where vtables point to base class vtables. It does not solve the other parts and I don't recall reading about these other parts outside of solutions based on classes. ...an operation on a list of vanilla objects that yields a class-like object
Are you asking for built-in support for "mixins"?
On Fri, Dec 16, 2011 at 4:22 AM, Axel Rauschmayer <axel at rauschma.de> wrote:
... How you work with object exemplars is almost like with classes: You have to be aware which objects are exemplars and which ones are instances. A useful sanity check could be to require an exemplar to have a constructor() method.
I'll just re-state this paragraph to illustrate that the glass is half empty:
Unlike classes, object exemplars are just objects. There is no language support for detecting a object exemplar; developers have no help in avoiding pernicious debilitating bugs caused by data values in base classes.
Note that there are always two steps involved in subtyping:
- Extend the shared properties.
- Extend the instance properties.
99% of the time this is not what developers want or need. They want to
extend the shared functions, not the shared properties.
The <| operation solves the same problem that seflish solves: behavior
inheritance; they both use object literals (optional in selfish); neither address the data-property specification problem nor the data-property initialization problem.
Yes, you have to know that instance properties are always added in the constructor. But that rule is the same with most class declaration proposals that exist. I don’t find that rule problematic, it’s just not how things are commonly done in mainstream languages – which tend to have a more declarative syntax for instance members.
And I agree that the extra syntax is not good, but the result is very good. Is there another way? That is the challenge I am posing here.
jjb
I'll just re-state this paragraph to illustrate that the glass is half empty:
Unlike classes, object exemplars are just objects. There is no language support for detecting a object exemplar; developers have no help in avoiding pernicious debilitating bugs caused by data values in base classes.
“There is no language support for detecting a object exemplar” – via static analysis? Yes, tricky, one could infer object exemplars by looking at the operands of "new".
Would you say that either of these problems exist with class declarations that have object exemplar semantics? => Easier to detect via static analysis. => Can forbid non-method properties in prototypes.
On Fri, Dec 16, 2011 at 7:42 AM, Axel Rauschmayer <axel at rauschma.de> wrote:
I'll just re-state this paragraph to illustrate that the glass is half empty:
Unlike classes, object exemplars are just objects. There is no language support for detecting a object exemplar; developers have no help in avoiding pernicious debilitating bugs caused by data values in base classes.
“There is no language support for detecting a object exemplar” – via static analysis? Yes, tricky, one could infer object exemplars by looking at the operands of "new".
(I believe that 'language support' effectively mean single-file static analysis, not whole program analysis implied by your trick).
Would you say that either of these problems exist with class declarations that have object exemplar semantics? => Easier to detect via static analysis. => Can forbid non-method properties in prototypes.
Sorry I don't understand the question, or how it is related to my quest. I'll answer two I think are related:
Is there a static-analysis test that can be applied to an object literal to determine if it is suitable as an object exemplar? Yes, forbid non-method properties. Is there a reasonable mechanism to go beyond a non-method-property restriction on object exemplars? Yes, use those fancy Object.defineProperty thingys to allow the 1% cases where you want non-method exemplar props.
jjb
Sorry I don't understand the question, or how it is related to my quest.
I would argue that your quest can be fulfilled by class declarations that desugar to object exemplars. Do you agree?
On Fri, Dec 16, 2011 at 9:01 AM, Axel Rauschmayer <axel at rauschma.de> wrote:
Sorry I don't understand the question, or how it is related to my quest.
I would argue that your quest can be fulfilled by class declarations that desugar to object exemplars. Do you agree?
Assuming that your class declarations resemble say Java's, then of course. But if we have class declarations like Java's why do we care about object exemplars?
jjb
I would argue that your quest can be fulfilled by class declarations that desugar to object exemplars. Do you agree?
Assuming that your class declarations resemble say Java's, then of course. But if we have class declarations like Java's why do we care about object exemplars?
Because a class declaration will desugar to something. And desugaring to an object exemplar is more elegant than desugaring to a function exemplar.
(I know the language experts know all this, I'm spelling it out for fellow amateur 'classless' fans)
After using (and liking) selfish I understand why prototypical inheritance via simple references is not an effective alternative to classes.
The new Foo/Foo.prototype JavaScript idiom sets up a lookup table like this:
[[Prototype]] -----> [[Prototype]] ------> [[Prototype]] -------> null
myProps | parentProps grandProps | | [[Prototype]] ----+ myProps
That is, two instances share a parent base class.
If you use these instances as values for a .prototype property or if you use these instances as the first argument to Object.create(), then these instances act as "classes". The [[Prototype]] links set up a single-inheritance "vtable".
However, repeating the process a second time fails. That is, using the object instances from the preceding paragraph as 'classes' almost always fails to produce a useful result. Every class is an object, but not every object is useful as a class.
The reason is simple: 99% of the time you want base class functions to be inherited but not base class data values. In setting up "objects-that-will-be-classes" we can consciously avoid data values. I did not say this above, but most readers will imagine that all of the objects in the diagram above have only functions. That is essential what we mean when we thing of these objects as classes: no data properties.
But once we use these classes to create instances, those instances will very likely have data. Thus they will no longer be effective as 'classes'.
In real JS code there is no way to tell if an object is intended to be a class. Objects that implement useful behavior seem like great candidates for "prototypical inheritance". This burns every JS developer at one time or another. And probably much more than most will admit.
JS projects address this issue by conventions and they can be successful. One in particular is 'selfish': the split between behavior and data is created by declaring behavior but implementing all data values imperatively in initialize(). It is convention only.
Class-based languages solve this by building the 'behavior' or function layer differently from the 'data' layer. The functions are placed in vtable data structures referencing base class vtables; data values are concatenated across all classes as properties for the instance.
Classless solutions need a compelling solution to this issue, that is, an operation on a list of vanilla objects that yields a class-like object. Any such operation will fail in some cases, just like class-based solutions fail sometimes (when you really want to share data across classes). But a solution is needed for the 99% of the time when you don't want data sharing.
jjb