Using Object Literals as Classes
Kevin Smith wrote:
Rather than abuse your inbox, I published this as a blog entry:
blog.khs4473.com/2012/03/using-object-literals-as-classes.html
Summary: It has been suggested that extended object literals can be used to define class-like abstractions, and that consequently class syntax is not necessary. I analyze the implications from the point of view of a library developer.
Comments?
I've got a slightly different POV.
Yes, in a perfect world, there is no need for class constructs. All people coming to ES learn how great and powerful it is to have the prototypal inheritance which lets you do everything you did before and many more.
I cherish this power and simplicity.
But, in the real world, everyone (except for the little percentage of the ones willing to learn and go deeper) begin to rant "where are the classes" and then painfully create some "library for OO in Javascript".
I don't want to meat these all over. ES is fine as it is.
And to stop this "class library" proliferation (and creating code that is much more mutually readable) one should add class constructs into the language. But to make such construct well-blended into the language and its prototypal nature.
First thing is, if it performs well for at least 80% of the common cases, people will use it rather than "better, truer, real classy from [inseet your own] library".
And second, it is not a tragedy if such classes "leak". I would even tell it is a positive thing. If native ES.next classes are good enough to describe class concept, but at the same time slowly learn the must-have-classes developers that classes are nothing more than a decoration based on the more basic elements of the language, they slowly adopt it, step by step (singleton is probably the first to die in this battle ;-) ).
I, personally, would live perfectly fine without class. A little more than is already in proposals (<| for declarations; separation of parallel hierearchy from single hierarchy) will suffice for me.
But there are different, above-mentioned reasons why native class is a very good idea (of course, not a heavy implementation that bring something another beyond the rich language of ES.next object literals; but that fear could be probably buried - it seems heavy classes are not going in).
Le 16/03/2012 17:53, Herby Vojčík a écrit :
And second, it is not a tragedy if such classes "leak".
What do you mean exactly by "leak". Like private name leaking?
I'll do a 2 parts answer. First, I'll answer the blog post, second, I'll try to take a step back and discuss composition in JavaScript.
=== Part I ===
Le 16/03/2012 17:04, Kevin Smith a écrit :
Rather than abuse your inbox, I published this as a blog entry:
blog.khs4473.com/2012/03/using-object-literals-as-classes.html
Summary: It has been suggested that extended object literals can be used to define class-like abstractions, and that consequently class syntax is not necessary.
I'm not sure the conclusion has ever been that strict. Having the proto and extend operator version is not mutually exclusive with a class syntax. I don't remember having read that at least.
I analyze the implications from the point of view of a library developer.
Comments?
The "Class"-like libraries started to compensate missing things in the language specifically when it came to loosely-coupled composition of abstractions. New language features (the proto operator, the extend operator, maybe even traits if they make it to the language) are here to fill a gap in the language expressiveness.
I'm a bit surprised by the approach of the blog post which consists in studying how new features will fit in or help ameliorate a Class library.
"useMethod
calls a private method here. But does this work? Privately
named properties aren't enumerable and don't show up in
Object.getOwnPropertyNames. My class library works by reading the keys
from the input object and attaching them to a new object with the
correct prototype chain. But..." (emphasis added)
=> your library works in a certain way and you won't be able to use your
exact implementation in concordance with how private names have been defined. Maybe the private name feature could be reconsidered or maybe your implementation could be reconsidered.
_new : function(textview){ TextRange.call(this); EventTarget.call(this); EventSubscriber.call(this);
this.blabla = ...
}
When I read this code, questions that comes to mind : What if conflicting names are defined in the TextRange, EventTarget or EventSubscriber? Does the order matter? Certainly so and it would make your composition mechanism not that declarative. Anyway, the answers do not really matter. What does is that it's not obvious from reading the code what happens. It's fine-ish for a library of your choice, but it's annoying when discussing language features.
"super.constructor
is annoyingly verbose, and I might as well just use
BaseClass.call(this). No gains there."
=> super will be a keyword. As such, it can be highlighted by IDEs. It
can help readability. It also avoids the repetition:
{ extends: TextRange, _new: function(textView) { TextRange.call(this); } }
With super, you know you're calling the thing you extend from. Change it in one place, it applies everywhere unlike in your case where you have to remember to change something in 2 places.
" For library authors, object literal extensions do not provide a replacement for class construction libraries." => This conclusion is rather surprising. You haven't assessed object
literal extensions to solve a problem, but just tried to see how they impact your Class construction library.
"The individual extensions provide some support in the form of shorter method syntax and static delegation, but fall short of providing the tools to create a fully featured class library. " => It wasn't a goal. If creating a fully-featured class library had been
a goal, I think there would be a strawman for that.
"Furthermore, it's not clear at the present time that prototype delegation is useful outside of the context of class-like abstractions, and the necessity of rebinding "super" for every method indicates that the "super" semantics are being defined at the wrong level of abstraction." => ... or that you're trying to use a feature in a way it was not design
for. In which case, don't use the feature.
=== Part II ===
So, let's take a step back. Before Class libraries, JavaScript did not have an easy way to express something like: "I want a Ninja to be a Person with a sword".
I can think of these ways (not an exhaustive list):
// 1) with methods as own properties of persons function Ninja(){ Person.call(this, "Joe"); // getting all persons properties // This style forces a bit objects to only be normal objects // They can't be arrays, functions, DOM objects or proxies for instance this.sword = "Excalibur"; }
// 2) with methods as own properties of persons function Ninja(){ var init = {}; // could be anything, like arrays or functions. var p = new Person("Emily"); // pour all p properties in 'init' with a for-in loop or whatever method // alternatively copy values and forward methods this.sword = "Excalibur"; }
// 3) with person methods in Person.prototype function Ninja(){ Person.call(this); // just properties this.sword = "Excalibur";
} Ninja.prototype = Object.create(Person.prototype) Ninja.prototype.method = function(){}; /* Encapsulation is lost in that case since the inherited methods need to know the state of the object which has to be public */
All of this is awkward to say the least. Comments and discussion welcome on this list.
Came along Class libraries which provided something a bit more declarative, but had one of the limitations of the above method anyway.
Fast forward to 2012. How do I define a Ninja with the new operators?
var Ninja = Person <| function(){ super.constructor(); this.sword = "Excalibur"; }
Encapsulation will not be compromised. Additionally private names will work fine here. If the base class is Array or WeakMap, it works as well.
In the end, JavaScript is still not a good language for classes library, but I think the new operators help solving most (maybe not all) problems Class libraries were initially introduced for.
David Bruant wrote:
Le 16/03/2012 17:53, Herby Vojčík a écrit :
And second, it is not a tragedy if such classes "leak". What do you mean exactly by "leak". Like private name leaking?
Some of the underlying mechanics.
Thanks, David.
First, I'd like to point out that in the blog post I'm putting on the hat of a library author (I happen to be one, but that's beside the point).
One of the first conclusions that I come to (as a library author) is that the idiom described here:
harmony:object_extension_literal_class_pattern
provides a worse user experience than what I already have. The goal post for any class syntax or idiom is not this:
function Blah() Blah.prototype = Object.create(BaseClass.prototype); Blah.prototype.a = function() {}; Blah.prototype.b = function() {};
It is rather this:
var MyClass = new Class({
extends: BaseClass,
a: function() {},
b: function() {}
});
And in the post I conclude fairly early on that the "object extension literal class pattern" described above does not meet or exceed this goal post, not by a long shot.
Now, if object literal extensions were such that I could use them in a "class" library (any class library), then that would be fine. But as indicated in the post, they cannot. The full advantages to be derived from object literal extensions with respect to class construction are unbreakably bound to the <| operator.
As such <| does not fill in missing low-level pieces of the puzzle, so much as dictate how higher level abstractions must be built. I cannot build a higher level class abstraction from fundamentals without leaking the implementation of that abstraction.
The answer cannot be that I ought not build higher level abstractions.
If the answer is that we'll have a class syntax, then great. But then we have to ask the related question, should "super" et. all defined on the object literal level? And do we even need special <| or .{} syntax, then?
Again, thanks David for reading and replying. I have great respect for everyone's ideas, and I'm just trying to fully explore this one.
On Fri, Mar 16, 2012 at 4:26 PM, Kevin Smith <khs4473 at gmail.com> wrote:
Thanks, David.
First, I'd like to point out that in the blog post I'm putting on the hat of a library author (I happen to be one, but that's beside the point).
One of the first conclusions that I come to (as a library author) is that the idiom described here:
harmony:object_extension_literal_class_pattern
provides a worse user experience than what I already have. The goal post for any class syntax or idiom is not this:
function Blah() Blah.prototype = Object.create(BaseClass.prototype); Blah.prototype.a = function() {}; Blah.prototype.b = function() {};
It is rather this:
var MyClass = new Class({ extends: BaseClass, a: function() {}, b: function() {} });
And in the post I conclude fairly early on that the "object extension literal class pattern" described above does not meet or exceed this goal post, not by a long shot.
+1, agreed, not by a long shot
Now, if object literal extensions were such that I could use them in a "class" library (any class library), then that would be fine. But as indicated in the post, they cannot. The full advantages to be derived from object literal extensions with respect to class construction are unbreakably bound to the <| operator.
As such <| does not fill in missing low-level pieces of the puzzle, so much as dictate how higher level abstractions must be built. I cannot build a higher level class abstraction from fundamentals without leaking the implementation of that abstraction.
The answer cannot be that I ought not build higher level abstractions.
If the answer is that we'll have a class syntax, then great. But then we have to ask the related question, should "super" et. all defined on the object literal level? And do we even need special <| or .{} syntax, then?
Again, thanks David for reading and replying. I have great respect for everyone's ideas, and I'm just trying to fully explore this one.
I am also a library author, and yes I've also created yet another class abstraction (seemed like a good idea at the time, 5 years ago) and I have to say, I totally agree. As much as I want to take advantage of <|, its just not good enough at the high or the low level. Because it has to take a literal on the RHS, it can't be used inside a class abstraction library, and because its still so imperative, its not very good at the high level.
I think the people that will really be able to take advantage of this will be language designers that target JS. CoffeeScript could probably put this to good use.
Just to contribute to this... er... fun-thread...
My team uses the closure pattern for our "classes" (i.e. no prototype methods at all), since we value encapsulation. I can't imagine we're alone.
So any class solution that doesn't fully incorporate private names (e.g. by making them awkward via manual Name.create(), etc.) will leave that audience behind, still using closures and ignoring any new class sugar.
Le 16/03/2012 21:26, Kevin Smith a écrit :
Thanks, David.
First, I'd like to point out that in the blog post I'm putting on the hat of a library author (I happen to be one, but that's beside the point).
One of the first conclusions that I come to (as a library author) is that the idiom described here:
harmony:object_extension_literal_class_pattern
provides a worse user experience than what I already have. The goal post for any class syntax or idiom is not this:
function Blah() Blah.prototype = Object.create(BaseClass.prototype); Blah.prototype.a = function() {}; Blah.prototype.b = function() {};
With new syntax, it would be:
var Blah = BaseClass <| function(){}
Blah.prototype.{
a: function(){},
b: function(){}
}
It seems we're rather close to what you describe:
It is rather this:
var MyClass = new Class({ extends: BaseClass, a: function() {}, b: function() {} });
And in the post I conclude fairly early on that the "object extension literal class pattern" described above does not meet or exceed this goal post, not by a long shot.
Now, if object literal extensions were such that I could use them in a "class" library (any class library), then that would be fine. But as indicated in the post, they cannot. The full advantages to be derived from object literal extensions with respect to class construction are unbreakably bound to the <| operator.
I guess what I'm failing to understand is why it's so important to have class libraries.
I tried, in the second part of my answer, to explain what led to class libraries. For that, I explained that they came from missing fundamental constructs in the language for abstraction composition. I then tried to show how new constructs help to solve the abstraction composition problem. Assuming my demonstration was good, it seems to me that trying to have both class libraries and using the new operators is a contradictory idea. It's trying to solve one problem with 2 solutions. Like having 2 roofs for the same house.
I think object literal operators fail at being usable with class libraries specifically because they are here to replace them, not work in conjonction with them. But that's just my opinion.
So, assuing we can restart from scratch with the JavaScript, are class libraries necessary? If so, why? What are the operators missing?
As such <| does not fill in missing low-level pieces of the puzzle, so much as dictate how higher level abstractions must be built. I cannot build a higher level class abstraction from fundamentals without leaking the implementation of that abstraction.
The answer cannot be that I ought not build higher level abstractions.
I can only agree. The language is here to serve us expressing our needs. If we are forced to limit our needs, the language should be changed... which is what we're talking about here :-)
But what I'm trying to understand is the need for classes. The way I see it, classes are not a solution in themselves, but rather an approach to express encapsulation, express composition and allow code reuse. It seems that the new operators are just a different approach which is purely object-based with other advantages and inconvenients.
If the answer is that we'll have a class syntax, then great. But then we have to ask the related question, should "super" et. all defined on the object literal level? And do we even need special <| or .{} syntax, then?
Again, thanks David for reading and replying.
Thanks for your initial post.
I have great respect for everyone's ideas, and I'm just trying to fully explore this one.
Likewise. I've never really felt the need for a class library, but I'm definitely interested in exploring if they are still needed with the new language constructs and if so, how the language could be improved. In my opinion, encapsulation and composition should be expressable by the language since they are so fundamental. If a library is needed to express something related to these concerns, it's certainly an indication that something is missing in the language.
On Fri, Mar 16, 2012 at 5:12 PM, Domenic Denicola < domenic at domenicdenicola.com> wrote:
Just to contribute to this... er... fun-thread...
My team uses the closure pattern for our "classes" (i.e. no prototype methods at all), since we value encapsulation. I can't imagine we're alone.
For my own curiosity, can you point me to some examples where you are strategically avoiding the use of the prototype pattern?
Le 16/03/2012 23:00, Rick Waldron a écrit :
On Fri, Mar 16, 2012 at 5:12 PM, Domenic Denicola <domenic at domenicdenicola.com <mailto:domenic at domenicdenicola.com>> wrote:
Just to contribute to this... er... fun-thread... My team uses the closure pattern for our "classes" (i.e. no prototype methods at all), since we value encapsulation. I can't imagine we're alone.
For my own curiosity, can you point me to some examples where you are strategically avoiding the use of the prototype pattern?
When he needs actual encapsulation. Unfortunately, methods on prototype require to have properties that are public.
If you avoid prototype methods, all your attributes and private methods can be shared by public method scopes.
On Fri, Mar 16, 2012 at 6:04 PM, David Bruant <bruant.d at gmail.com> wrote:
Le 16/03/2012 23:00, Rick Waldron a écrit :
On Fri, Mar 16, 2012 at 5:12 PM, Domenic Denicola < domenic at domenicdenicola.com> wrote:
Just to contribute to this... er... fun-thread...
My team uses the closure pattern for our "classes" (i.e. no prototype methods at all), since we value encapsulation. I can't imagine we're alone.
For my own curiosity, can you point me to some examples where you are strategically avoiding the use of the prototype pattern?
When he needs actual encapsulation. Unfortunately, methods on prototype require to have properties that are public.
If you avoid prototype methods, all your attributes and private methods can
be shared by public method scopes.
Sorry, I don't subscribe to this as an adequate argument against prototypes. jQuery has a whole lot of hidden, private functions and data - using an IIFE. Ultimately, the developer makes the decision to write well encapsulated code - prototype or closure pattern should have no bearing.
On Mar 16, 2012, at 18:05, "David Bruant" <bruant.d at gmail.com<mailto:bruant.d at gmail.com>> wrote:
Le 16/03/2012 23:00, Rick Waldron a écrit : On Fri, Mar 16, 2012 at 5:12 PM, Domenic Denicola <domenic at domenicdenicola.com<mailto:domenic at domenicdenicola.com>> wrote:
Just to contribute to this... er... fun-thread...
My team uses the closure pattern for our "classes" (i.e. no prototype methods at all), since we value encapsulation. I can't imagine we're alone.
For my own curiosity, can you point me to some examples where you are strategically avoiding the use of the prototype pattern? When he needs actual encapsulation. Unfortunately, methods on prototype require to have properties that are public.
If you avoid prototype methods, all your attributes and private methods can be shared by public method scopes.
David
Exactly. I am away from computer right now, so I cannot dig up examples from our codebase at work, but essentially we use the pattern described by Crockford in
www.crockford.com/javascript/private.html
And we use only "privileged" methods (on the instance), not "public" ones (on the prototype). This also neatly avoids binding problems via a single var that = this
at the top of the constructor.
Here is an example from some of my own open-source code:
domenic/pubit/blob/master/lib/Publisher.js
(The var that = this
in that particular example is unnecessary since no public methods call each other; it's just there as part of the boilerplate encapsulated-class pattern I always use.)
Hope this helps, despite being cobbled together on an iPhone.
On Mar 16, 2012, at 18:12, "Rick Waldron" <waldron.rick at gmail.com<mailto:waldron.rick at gmail.com>> wrote:
On Fri, Mar 16, 2012 at 6:04 PM, David Bruant <bruant.d at gmail.com<mailto:bruant.d at gmail.com>> wrote:
Le 16/03/2012 23:00, Rick Waldron a écrit : On Fri, Mar 16, 2012 at 5:12 PM, Domenic Denicola <domenic at domenicdenicola.com<mailto:domenic at domenicdenicola.com>> wrote:
Just to contribute to this... er... fun-thread...
My team uses the closure pattern for our "classes" (i.e. no prototype methods at all), since we value encapsulation. I can't imagine we're alone.
For my own curiosity, can you point me to some examples where you are strategically avoiding the use of the prototype pattern? When he needs actual encapsulation. Unfortunately, methods on prototype require to have properties that are public.
If you avoid prototype methods, all your attributes and private methods can be shared by public method scopes.
Sorry, I don't subscribe to this as an adequate argument against prototypes. jQuery has a whole lot of hidden, private functions and data - using an IIFE. Ultimately, the developer makes the decision to write well encapsulated code - prototype or closure pattern should have no bearing.
Rick
David
That only works for singleton modules, not multi-instance classes.
WeakMaps and private names both provide solutions for associating private data to an instance, without abandoning prototypes, but that brings me back to my original point: class sugar must make such association easier than manual Name.create()
usage, on par with the ease of the closure pattern.
Le 16/03/2012 23:12, Rick Waldron a écrit :
On Fri, Mar 16, 2012 at 6:04 PM, David Bruant <bruant.d at gmail.com <mailto:bruant.d at gmail.com>> wrote:
Le 16/03/2012 23:00, Rick Waldron a écrit :
On Fri, Mar 16, 2012 at 5:12 PM, Domenic Denicola <domenic at domenicdenicola.com <mailto:domenic at domenicdenicola.com>> wrote: Just to contribute to this... er... fun-thread... My team uses the closure pattern for our "classes" (i.e. no prototype methods at all), since we value encapsulation. I can't imagine we're alone. For my own curiosity, can you point me to some examples where you are strategically avoiding the use of the prototype pattern?
When he needs actual encapsulation. Unfortunately, methods on prototype require to have properties that are public. If you avoid prototype methods, all your attributes and private methods can be shared by public method scopes.
Sorry, I don't subscribe to this as an adequate argument against prototypes. jQuery has a whole lot of hidden, private functions and data - using an IIFE. Ultimately, the developer makes the decision to write well encapsulated code - prototype or closure pattern should have no bearing.
I agree that it works with one prototype object, but it doesn't scale. The problem does not come from either prototype inheritance nor encapsulation, but trying to have them both (and several layers of inheritance).
If I want to create my own library "on top" of jQuery and that my objects have the standard jQuery API + my own functions, I can, but I won't have access to the internals of jQuery. I'm restricted to the public API, which can be a limitation. Java has "protected" that allows a subclass to have access to the internals of another class to solve this issue. I can also add my things to $.fn... but... $.fn.galery is probably taken by half a billion libraries already :-)
I intuit that with private name, it will be possible to have encapsulation and several layer of inheritance.
With current JavaScript, you can have the sort of encapsulation+inheritance that you want without private names, but it will take ugly code using weakmaps (or a good ES5-compatible polyfill of them).
On Fri, Mar 16, 2012 at 3:04 PM, David Bruant <bruant.d at gmail.com> wrote:
Unfortunately, methods on prototype require to have properties that are public.
If you avoid prototype methods, all your attributes and private methods can be shared by public method scopes.
I think you are mixing up interface and implementation here, in a funky way ;-)
The interface of the 'prototype', a pointer to a table for fallback lookup, allows implementation inheritance. But it does not demand it, it's just a choice you may make.
You can, for example, create an implementation hierarchy, then encapsulate it in a an interface hierarchy. The functions on the .prototype for the interface may be pointers to functions on the encapsulated implementation.
Something like:
var Foo = (function makeFoo(impl) { function Foo() { impl.call(this, ...); } Foo.prototype = { bar: impl.bar, baz: impl.baz }; return Foo; })(theImpl);
So prototype does not mean non-encapsulation.
jjb
Just to contribute to this... er... fun-thread...
My team uses the closure pattern for our "classes" (i.e. no prototype methods at all), since we value encapsulation. I can't imagine we're alone.
Do you see privacy via closures and prototypes as mutually exclusive? In my experience, they work well in combination. Do you add all of your methods to the instance? That would have the advantage of making it easier to access private data from public methods, but would consume more memory.
So any class solution that doesn't fully incorporate private names (e.g. by making them awkward via manual Name.create(), etc.) will leave that audience behind, still using closures and ignoring any new class sugar.
True. Something along the lines of:
class C {
private {
age, strength
}
/* syntactic sugar for
module name from "@name";
var age = name.create();
var strength = name.create();
*/
// private method
@incAge() {
this. at age++;
}
}
On Fri, Mar 16, 2012 at 6:20 PM, Domenic Denicola < domenic at domenicdenicola.com> wrote:
On Mar 16, 2012, at 18:12, "Rick Waldron" <waldron.rick at gmail.com> wrote:
On Fri, Mar 16, 2012 at 6:04 PM, David Bruant <bruant.d at gmail.com> wrote:
Le 16/03/2012 23:00, Rick Waldron a écrit :
On Fri, Mar 16, 2012 at 5:12 PM, Domenic Denicola < domenic at domenicdenicola.com> wrote:
Just to contribute to this... er... fun-thread...
My team uses the closure pattern for our "classes" (i.e. no prototype methods at all), since we value encapsulation. I can't imagine we're alone.
For my own curiosity, can you point me to some examples where you are strategically avoiding the use of the prototype pattern?
When he needs actual encapsulation. Unfortunately, methods on prototype require to have properties that are public.
If you avoid prototype methods, all your attributes and private methods
can be shared by public method scopes.
Sorry, I don't subscribe to this as an adequate argument against prototypes. jQuery has a whole lot of hidden, private functions and data - using an IIFE. Ultimately, the developer makes the decision to write well encapsulated code - prototype or closure pattern should have no bearing.
Rick
David
That only works for singleton modules, not multi-instance classes.
Multi-instance as in many instances created from a "class"? Every call to jQuery or its alias $ actually produces a new, unique object instance from a real constructor.
The example you gave produces a constructor that wraps a handful of instance method definitions along with several function declarations - which I'm arguing could just as easily be outside of that function declaration, but inside of an IIFE.
From: Rick Waldron [mailto:waldron.rick at gmail.com] Sent: Friday, March 16, 2012 18:40 To: Domenic Denicola Cc: David Bruant; es-discuss Subject: Re: Using Object Literals as Classes
On Fri, Mar 16, 2012 at 6:20 PM, Domenic Denicola <domenic at domenicdenicola.com<mailto:domenic at domenicdenicola.com>> wrote:
On Mar 16, 2012, at 18:12, "Rick Waldron" <waldron.rick at gmail.com<mailto:waldron.rick at gmail.com>> wrote:
On Fri, Mar 16, 2012 at 6:04 PM, David Bruant <bruant.d at gmail.com<mailto:bruant.d at gmail.com>> wrote:
Le 16/03/2012 23:00, Rick Waldron a écrit : On Fri, Mar 16, 2012 at 5:12 PM, Domenic Denicola <domenic at domenicdenicola.com<mailto:domenic at domenicdenicola.com>> wrote:
Just to contribute to this... er... fun-thread...
My team uses the closure pattern for our "classes" (i.e. no prototype methods at all), since we value encapsulation. I can't imagine we're alone.
For my own curiosity, can you point me to some examples where you are strategically avoiding the use of the prototype pattern? When he needs actual encapsulation. Unfortunately, methods on prototype require to have properties that are public.
If you avoid prototype methods, all your attributes and private methods can be shared by public method scopes.
Sorry, I don't subscribe to this as an adequate argument against prototypes. jQuery has a whole lot of hidden, private functions and data - using an IIFE. Ultimately, the developer makes the decision to write well encapsulated code - prototype or closure pattern should have no bearing.
Rick
David
That only works for singleton modules, not multi-instance classes.
Multi-instance as in many instances created from a "class"? Every call to jQuery or its alias $ actually produces a new, unique object instance from a real constructor.
The example you gave produces a constructor that wraps a handful of instance method definitions along with several function declarations - which I'm arguing could just as easily be outside of that function declaration, but inside of an IIFE.
There are two issues here:
- “Private methods” could be moved outside to the module level (IIFE level if you wish).
This is true, but would necessitate passing the private state into these methods: callListener
would need options
, which then propagates to an extra parameter on callListenersForSync
and callListenersForAsync
. Obviously this problem multiplies as you add more private state to the object, eventually ending up passing around a “secrets” object containing all your private state, which is a bundle of joy to program against. </sarcasm>
- “Public methods” cannot be moved outside to the prototype level.
If instead of that.publish = function () { ... }
inside the constructor, I had Publisher.prototype.publish = function () { ... }
outside the constructor, then the function body could not access the “private instance variables” normalListeners
and oneTimeListeners
, or the constructor parameter options
.
The only way to allow a Publisher.prototype.publish
method to access this per-instance state would be to un-encapsulate it, making it public so that they could be accessed as e.g. this._normalListeners
.
This is what I mean when I say I eschew the prototypal pattern in favor of the closure pattern. All of my methods are instance methods, because they need access to private, encapsulated state.
From: Axel Rauschmayer [mailto:axel at rauschma.de] Sent: Friday, March 16, 2012 18:35 To: Domenic Denicola Cc: Russell Leggett; Kevin Smith; es-discuss Subject: Re: Using Object Literals as Classes
Just to contribute to this... er... fun-thread...
My team uses the closure pattern for our "classes" (i.e. no prototype methods at all), since we value encapsulation. I can't imagine we're alone.
Do you see privacy via closures and prototypes as mutually exclusive? In my experience, they work well in combination. Do you add all of your methods to the instance? That would have the advantage of making it easier to access private data from public methods, but would consume more memory.
In theory I see them as complementary, but in practice I have never been able to integrate prototypes. Consider: a method can only be attached to a prototype if it needs access to an instance’s public state only, and not to its private state. These cases are extremely rare. And when they do occur, I usually ask myself—is this really an instance-level method? Why not make it a module-level method that operates on instances of my class?
We have so far made the trade-off of sacrificing memory for encapsulation. (Although, I still find it strange that modern JITers don’t pull out of the code of the method to reuse in each instance, and instead duplicate it every time. Cf. the “Joining” section of 1.) It hasn’t bitten us yet.
So any class solution that doesn't fully incorporate private names (e.g. by making them awkward via manual Name.create(), etc.) will leave that audience behind, still using closures and ignoring any new class sugar.
True. Something along the lines of:
class C {
private {
age, strength
}
/* syntactic sugar for
module name from "@name";
var age = name.create();
var strength = name.create();
*/
// private method
@incAge() {
this. at age<mailto:this. at age>++;
}
}
This does look familiar, and quite useful. But it’s certainly outside the scope of “object literals as classes,” which I think ties to my original point in supporting the OP that object literals alone are not sufficient.
On Mar 16, 2012, at 7:11 PM, Domenic Denicola <domenic at domenicdenicola.com> wrote:
From: Rick Waldron [mailto:waldron.rick at gmail.com] Sent: Friday, March 16, 2012 18:40 To: Domenic Denicola Cc: David Bruant; es-discuss Subject: Re: Using Object Literals as Classes
On Fri, Mar 16, 2012 at 6:20 PM, Domenic Denicola <domenic at domenicdenicola.com> wrote:
On Mar 16, 2012, at 18:12, "Rick Waldron" <waldron.rick at gmail.com> wrote:
On Fri, Mar 16, 2012 at 6:04 PM, David Bruant <bruant.d at gmail.com> wrote: Le 16/03/2012 23:00, Rick Waldron a écrit : On Fri, Mar 16, 2012 at 5:12 PM, Domenic Denicola <domenic at domenicdenicola.com> wrote: Just to contribute to this... er... fun-thread...
My team uses the closure pattern for our "classes" (i.e. no prototype methods at all), since we value encapsulation. I can't imagine we're alone.
For my own curiosity, can you point me to some examples where you are strategically avoiding the use of the prototype pattern? When he needs actual encapsulation. Unfortunately, methods on prototype require to have properties that are public.
If you avoid prototype methods, all your attributes and private methods can be shared by public method scopes.
Sorry, I don't subscribe to this as an adequate argument against prototypes. jQuery has a whole lot of hidden, private functions and data - using an IIFE. Ultimately, the developer makes the decision to write well encapsulated code - prototype or closure pattern should have no bearing.
Rick
David
That only works for singleton modules, not multi-instance classes.
Multi-instance as in many instances created from a "class"? Every call to jQuery or its alias $ actually produces a new, unique object instance from a real constructor.
The example you gave produces a constructor that wraps a handful of instance method definitions along with several function declarations - which I'm arguing could just as easily be outside of that function declaration, but inside of an IIFE.
There are two issues here:
- “Private methods” could be moved outside to the module level (IIFE level if you wish).
This is true, but would necessitate passing the private state into these methods:
callListener
would needoptions
, which then propagates to an extra parameter oncallListenersForSync
andcallListenersForAsync
. Obviously this problem multiplies as you add more private state to the object, eventually ending up passing around a “secrets” object containing all your private state, which is a bundle of joy to program against. </sarcasm>
There is more then one way to skin a goose...
- “Public methods” cannot be moved outside to the prototype level.
I should've used a period, or perhaps a whole new paragraph, because I never meant to imply that they could or should.
If instead of
that.publish = function () { ... }
inside the constructor, I hadPublisher.prototype.publish = function () { ... }
outside the constructor, then the function body could not access the “private instance variables”normalListeners
andoneTimeListeners
, or the constructor parameteroptions
.
Inside the IIFE, WeakMap (or shimmed equivalent) with instance as key, pointing to an object with all of those goodies stored in it.
The only way to allow a
Publisher.prototype.publish
method to access this per-instance state would be to un-encapsulate it, making it public so that they could be accessed as e.g.this._normalListeners
.
See WeakMap strategy above
This is what I mean when I say I eschew the prototypal pattern in favor of the closure pattern. All of my methods are instance methods, because they need access to private, encapsulated state.
I still say that this can all be accomplished with prototypes and IIFEs.
On Fri, Mar 16, 2012 at 4:23 PM, Domenic Denicola <domenic at domenicdenicola.com> wrote:
In theory I see them as complementary, but in practice I have never been able to integrate prototypes. Consider: a method can only be attached to a prototype if it needs access to an instance’s public state only, and not to its private state. These cases are extremely rare. And when they do occur, I usually ask myself—is this really an instance-level method? Why not make it a module-level method that operates on instances of my class?
Note that private Names let you finally get a prototypal method that accesses instance-private data; you create the private Name inside an IIFE along with the constructor and the prototype methods, and use it to access data. Outside the IIFE no one can get to it.
(WeakMaps provide the same ability through slightly different functionality, as Rick points out.)
From: Rick Waldron [mailto:waldron.rick at gmail.com] Sent: Friday, March 16, 2012 19:28 To: Domenic Denicola Cc: David Bruant; es-discuss Subject: Re: Using Object Literals as Classes
On Mar 16, 2012, at 7:11 PM, Domenic Denicola <domenic at domenicdenicola.com<mailto:domenic at domenicdenicola.com>> wrote:
From: Rick Waldron [mailto:waldron.rick at gmail.com]<mailto:[mailto:waldron.rick at gmail.com]>
Sent: Friday, March 16, 2012 18:40 To: Domenic Denicola Cc: David Bruant; es-discuss Subject: Re: Using Object Literals as Classes
On Fri, Mar 16, 2012 at 6:20 PM, Domenic Denicola <domenic at domenicdenicola.com<mailto:domenic at domenicdenicola.com>> wrote:
On Mar 16, 2012, at 18:12, "Rick Waldron" <waldron.rick at gmail.com<mailto:waldron.rick at gmail.com>> wrote:
On Fri, Mar 16, 2012 at 6:04 PM, David Bruant <bruant.d at gmail.com<mailto:bruant.d at gmail.com>> wrote:
Le 16/03/2012 23:00, Rick Waldron a écrit : On Fri, Mar 16, 2012 at 5:12 PM, Domenic Denicola <domenic at domenicdenicola.com<mailto:domenic at domenicdenicola.com>> wrote:
Just to contribute to this... er... fun-thread...
My team uses the closure pattern for our "classes" (i.e. no prototype methods at all), since we value encapsulation. I can't imagine we're alone.
For my own curiosity, can you point me to some examples where you are strategically avoiding the use of the prototype pattern? When he needs actual encapsulation. Unfortunately, methods on prototype require to have properties that are public.
If you avoid prototype methods, all your attributes and private methods can be shared by public method scopes.
Sorry, I don't subscribe to this as an adequate argument against prototypes. jQuery has a whole lot of hidden, private functions and data - using an IIFE. Ultimately, the developer makes the decision to write well encapsulated code - prototype or closure pattern should have no bearing.
Rick
David
That only works for singleton modules, not multi-instance classes.
Multi-instance as in many instances created from a "class"? Every call to jQuery or its alias $ actually produces a new, unique object instance from a real constructor.
The example you gave produces a constructor that wraps a handful of instance method definitions along with several function declarations - which I'm arguing could just as easily be outside of that function declaration, but inside of an IIFE.
There are two issues here:
- “Private methods” could be moved outside to the module level (IIFE level if you wish).
This is true, but would necessitate passing the private state into these methods: callListener
would need options
, which then propagates to an extra parameter on callListenersForSync
and callListenersForAsync
. Obviously this problem multiplies as you add more private state to the object, eventually ending up passing around a “secrets” object containing all your private state, which is a bundle of joy to program against. </sarcasm>
There is more then one way to skin a goose...
- “Public methods” cannot be moved outside to the prototype level.
I should've used a period, or perhaps a whole new paragraph, because I never meant to imply that they could or should.
If instead of that.publish = function () { ... }
inside the constructor, I had Publisher.prototype.publish = function () { ... }
outside the constructor, then the function body could not access the “private instance variables” normalListeners
and oneTimeListeners
, or the constructor parameter options
.
Inside the IIFE, WeakMap (or shimmed equivalent) with instance as key, pointing to an object with all of those goodies stored in it.
The only way to allow a Publisher.prototype.publish
method to access this per-instance state would be to un-encapsulate it, making it public so that they could be accessed as e.g. this._normalListeners
.
See WeakMap strategy above
This is what I mean when I say I eschew the prototypal pattern in favor of the closure pattern. All of my methods are instance methods, because they need access to private, encapsulated state.
I still say that this can all be accomplished with prototypes and IIFEs.
I agree, it can all be done with WeakMaps or private names combined with modules and IIFEs, as I pointed out a few emails back and as Tab points out in another reply. But at that point (especially with WeakMaps, but also with unsugared private names) I am jumping through enough hoops that I’d rather just go the closure route.
In my opinion, if ES6 delivers no sugar for private names, very few closure-pattern-using developers will jump through the necessary hoops to convert from closures to prototypes. (They’d also have to deal with losing the guaranteed that = this
binding, but that’s another story.) That’s really the only point I wanted to make in this thread; I think we got sidetracked talking past each other about implementation patterns in current ES5/ES5-plus-WeakMaps environments.
On Mar 16, 2012, at 7:37 PM, Domenic Denicola <domenic at domenicdenicola.com> wrote:
From: Rick Waldron [mailto:waldron.rick at gmail.com] Sent: Friday, March 16, 2012 19:28 To: Domenic Denicola Cc: David Bruant; es-discuss Subject: Re: Using Object Literals as Classes
On Mar 16, 2012, at 7:11 PM, Domenic Denicola <domenic at domenicdenicola.com> wrote:
From: Rick Waldron [mailto:waldron.rick at gmail.com] Sent: Friday, March 16, 2012 18:40 To: Domenic Denicola Cc: David Bruant; es-discuss Subject: Re: Using Object Literals as Classes
On Fri, Mar 16, 2012 at 6:20 PM, Domenic Denicola <domenic at domenicdenicola.com> wrote:
On Mar 16, 2012, at 18:12, "Rick Waldron" <waldron.rick at gmail.com> wrote:
On Fri, Mar 16, 2012 at 6:04 PM, David Bruant <bruant.d at gmail.com> wrote: Le 16/03/2012 23:00, Rick Waldron a écrit : On Fri, Mar 16, 2012 at 5:12 PM, Domenic Denicola <domenic at domenicdenicola.com> wrote: Just to contribute to this... er... fun-thread...
My team uses the closure pattern for our "classes" (i.e. no prototype methods at all), since we value encapsulation. I can't imagine we're alone.
For my own curiosity, can you point me to some examples where you are strategically avoiding the use of the prototype pattern? When he needs actual encapsulation. Unfortunately, methods on prototype require to have properties that are public.
If you avoid prototype methods, all your attributes and private methods can be shared by public method scopes.
Sorry, I don't subscribe to this as an adequate argument against prototypes. jQuery has a whole lot of hidden, private functions and data - using an IIFE. Ultimately, the developer makes the decision to write well encapsulated code - prototype or closure pattern should have no bearing.
Rick
David
That only works for singleton modules, not multi-instance classes.
Multi-instance as in many instances created from a "class"? Every call to jQuery or its alias $ actually produces a new, unique object instance from a real constructor.
The example you gave produces a constructor that wraps a handful of instance method definitions along with several function declarations - which I'm arguing could just as easily be outside of that function declaration, but inside of an IIFE.
There are two issues here:
- “Private methods” could be moved outside to the module level (IIFE level if you wish).
This is true, but would necessitate passing the private state into these methods:
callListener
would needoptions
, which then propagates to an extra parameter oncallListenersForSync
andcallListenersForAsync
. Obviously this problem multiplies as you add more private state to the object, eventually ending up passing around a “secrets” object containing all your private state, which is a bundle of joy to program against. </sarcasm>There is more then one way to skin a goose...
- “Public methods” cannot be moved outside to the prototype level.
I should've used a period, or perhaps a whole new paragraph, because I never meant to imply that they could or should.
If instead of
that.publish = function () { ... }
inside the constructor, I hadPublisher.prototype.publish = function () { ... }
outside the constructor, then the function body could not access the “private instance variables”normalListeners
andoneTimeListeners
, or the constructor parameteroptions
.Inside the IIFE, WeakMap (or shimmed equivalent) with instance as key, pointing to an object with all of those goodies stored in it.
The only way to allow a
Publisher.prototype.publish
method to access this per-instance state would be to un-encapsulate it, making it public so that they could be accessed as e.g.this._normalListeners
.See WeakMap strategy above
This is what I mean when I say I eschew the prototypal pattern in favor of the closure pattern. All of my methods are instance methods, because they need access to private, encapsulated state.
I still say that this can all be accomplished with prototypes and IIFEs.
I agree, it can all be done with WeakMaps or private names combined with modules and IIFEs, as I pointed out a few emails back and as Tab points out in another reply. But at that point (especially with WeakMaps, but also with unsugared private names) I am jumping through enough hoops that I’d rather just go the closure route.
In my opinion, if ES6 delivers no sugar for private names, very few closure-pattern-using developers will jump through the necessary hoops to convert from closures to prototypes. (They’d also have to deal with losing the guaranteed
that = this
Which can be avoided with .bind(this), today.
var Blah = BaseClass <| function(){}
Blah.prototype.{
a: function(){}, b: function(){}
}
The point I'm trying to drive home is that the above is no better (worse, in fact) from a user experience standpoint, than what I have now. To be blunt, I would not use the above construction - my library's API is better.
But what I'm trying to understand is the need for classes.
There is a school of thought that believes that it is always suboptimal to express encapsulation and reuse in Javascript in terms of "classical" classes. I do not subscribe to this belief. Sometimes (but not always) classes can be the right tool for the job.
ES3/5 gives us a programming model that includes constructors and prototypes. It does not give us "classes". With constructors and prototypes, though, we can create classes. I argue that this "stack" should be maintained. A class syntax would be great, but whether we get it or not, classes should be an abstraction built on top of constructors and prototypes. We should not allow class concepts (like super-delegation) to bleed down into the realm of object literals.
Of course, there's always the possibility that I'm completely wrong or babbling incoherently to myself. These are the risks one takes...
Thanks again for your time! : )
Kevin,
Thanks for taking the time to write up your thoughts1 in this area. I won't try to make a point by point response because I think others on this list are already doing a good job at this. However, I do what do clarify a few points about the intent and likely usage patterns of of various object literal extension proposal that I have been involved with.
First I'm not exactly clear on you position WRT including syntactic class declarations as a feature of the ECMAScript language. Are you in favor of that or do you think that library provided class abstractions such as your describe are preferable for defining abstractions over objects. If you think that adding syntactic class declarations are a good idea, then what specific class semantics do you require. You seem to have some pretty specific ideas (as exemplified in your library) about the semantics of classes. What if syntactic classes become "built-in", but they do not support all the features of your class model? For example, what if they only support single inheritance? Would you use them? Or would you continue to use your on library provide class abstraction?
The reason the above questions are important, is because many people think "classes" should be added to ECMAScript but there are many different things that they mean by "class". The biggest impediment to adding classes to ECMAScript out inability, up to this point, to find a class model that is satisfactory to all of the class proponents. Our experience so far, is that the more "full featured" a class model becomes, the more likely that it will include some feature that is considered essential by some parts of the community but is totally unacceptable to other members of the community. Yes, if a class model is minimized to the avoid such controversial features, nobody finds it to be worth having.
The object literal class pattern2, is an attempt to provide an "80%" percent solution for defining class like abstractions that can be used in the absence of a full featured declarative class definition. It does indeed assert (and demonstrate) that extended object literals can be used to define "class-like abstractions". However, it does not say, as you content, that "class syntax is not necessary". What it says is:
"This pattern seems to capture pretty much everything that the classes proposal accomplish without introducing a new hardwired syntactic forms. With this pattern available do we really need class declarations (at least for now). Rather than rushing syntactic class declarations into ES.next perhaps we should first get experience with this pattern and later, based upon that experience reconsider whether we need syntactic class declarations."3
Note the above is a reference to a single specific classes proposal and it is offering this pattern as a potentially less intrusive alternative for what was (and still is) a controversial proposal.
In your review you state: "It has been suggested that in ES.next extended object literal can be used to create class-like abstractions" and then investigate a more specific assertion "Can I get rid of my class library now, and use this built-in feature to define classes?". First note that the first sentence of 2 states that my proposed pattern "can be used to define class-like abstractions that have all of the characteristics of the built-in abstractions in Chapter 15 of the ECMAScript specification". It is claiming that it suffices for supporting one specific class-like abstraction model, the one that is implicit in Chapter 15. In fact, it is quite a bit more general than the chapter 15 class model, as is demonstrated (not just suggested) by 4 which uses the pattern to implement a well-known and fairly complex single inheritance class library.
But 2 does not assert that its pattern is suitable for supporting all class-like abstractions and in particular it makes no claims relating to your library classed class abstraction. It if fine for you to contrast the the features of the Chapter 15 class model (as support by my pattern) with your class model. But since supporting your pattern was not a goal of my pattern, its failure to do so doesn't really have much relevance to my stated goals.
More generally, you dig into whether the various object literal extensions that I use for this pattern would be useful for library developers who are defining class definition libraries. This is an interesting investigation, but supporting such library developers was also not one of the goals of my specific proposal. The patterns in 2 were intended to be used directly by ES programmers who wanted to express Chapter 15 type class-like abstractions directly in ES code without using a library. But supporting library writes is one of the Harmony goals, so I'd be quite interested in hearing from you about what new compositional features you think would be useful to such library developers.
Just one other points before I wrap up.
You criticize the pattern for not being declarative, "not in nature, not in form". I have to disagree with you on that point. Do you believe that your library Class abstraction is is declarative in either nature or form? When I compare your code in 5 with my code in 4 I don't see that much of a difference in their "declarativeness". On this point, I probably should define what I mean by "declarative". I consider code to be declarative, if a human (or an appropriately programmed machine) can understand its meaning without actually (or symbolically ) executing the code in its runtime environment. In other words, its meaning is apparent from inspection rather than from evaluation. My pattern uses a very small number of imperative operators (<| , .prototype , .{ .constructor this.( , etc. ) to define its class-like abstractions. While these are "imperative" when executed by the JS engine it is also possible to assign declarative meaning to them. You could build a front-end that recognized these declarative patterns and general the runtime structures to represent these "classes" without have to imperative execute the imperative definitions patterns. The same may well be true for your library pattern.
Again, thanks for taking the time to write this all up. Even if I don't agree with all of your conclusions the input is valuable. I'd like to seem more feedback like this from the community.
Allen
On Mar 16, 2012, at 6:05 PM, Kevin Smith wrote:
var Blah = BaseClass <| function(){} Blah.prototype.{ a: function(){}, b: function(){} }
The point I'm trying to drive home is that the above is no better (worse, in fact) from a user experience standpoint, than what I have now. To be blunt, I would not use the above construction - my library's API is better.
Going back to David's post that originated the above code. It was comparing it to:
function Blah() Blah.prototype = Object.create(BaseClass.prototype); Blah.prototype.a = function() {}; Blah.prototype.b = function() {};
which using my pattern would could actually be written like:
let Blah = BaseClass <| function() { }.prototype.( a() {}, b() {} }.constructor.{};
(BTW, there are other pattern variations we have explored, some of which are arguably better than this one...)
It is hard for me to see how this is worse than the the original code. Here are ways that it is better: It is one statement, not 4. It is well delimited, it you need to move it somewhere else it is easy to find the beginning and end. It is an easily recognized pattern not just a sequence of assignment expressions. The definitional elements, the name, and the superclass are on the first line It provides both instance and class side inheritance Methods aren't enumerable, don't unexpectedly show up in for-in looops Not shown: It provides a way to perform super construction introducing name coupling (easer to refactor class hierarchy) Not shown: Methods may to super invokes, again without introduce name coupling Not shown: It provides a place for class-side properties to be defined" It is more concise, with fewer noise words.
But what I'm trying to understand is the need for classes.
There is a school of thought that believes that it is always suboptimal to express encapsulation and reuse in Javascript in terms of "classical" classes. I do not subscribe to this belief. Sometimes (but not always) classes can be the right tool for the job.
ES3/5 gives us a programming model that includes constructors and prototypes. It does not give us "classes". With constructors and prototypes, though, we can create classes. I argue that this "stack" should be maintained. A class syntax would be great, but whether we get it or not, classes should be an abstraction built on top of constructors and prototypes. We should not allow class concepts (like super-delegation) to bleed down into the realm of object literals.
First, "super delegation" is not a "class concept", rather it is a fundamental concept of both inheritance or delegation based computational models. Look that the self language. You will find that it has super
Next, the above pattern is doing exactly what you are asking for. It is a pattern for defining and composing prototypes and constructors into to the class model that is used for all of the built-in ES object abstractions. The object literal extensions make this pattern more concise and expressive.
On Fri, Mar 16, 2012 at 6:28 PM, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:
On this point, I probably should define what I mean by "declarative". I consider code to be declarative, if a human (or an appropriately programmed machine) can understand its meaning without actually (or symbolically ) executing the code in its runtime environment. In other words, its meaning is apparent from inspection rather than from evaluation.
CSS is declarative. Anyone who codes CSS knows it's meaning is not apparent from inspection ;-).
I guess you don't mean 'declarative', but rather you mean what you said: 'meaning apparent by inspection'.
Imperative forms easily pass such a test; that is why we have libraries.
In all of these forms the developer must 'symbolically execute' the code to understand it. They must read top to bottom, left to right; as they proceed they build up a mental model of the system state. If the symbols or words are clear the reader and they understand the system, they will succeed.
So the axis of interest is not declarative vs imperative.
The issue that your test addresses is context-free readability by ordinary developers. Good syntax adds constraints and reduces text; library function calls offer flexibility and shallower learning curve. Good solutions can be found with either.
Sorry for the meta excursion, jjb
Thanks for taking the time to read and respond. I've read your reply in detail and I feel like it deserves a point-by-point response, but I think the following might get us on the same page more quickly.
The important point to consider is that I'm not comparing this:
A.)
let Blah = BaseClass <| function() { }.prototype.{ a() {} b() {} }.constructor.{};
to this:
B.)
function Blah()
Blah.prototype = Object.create(BaseClass.prototype);
Blah.prototype.a = function() {};
Blah.prototype.b = function() {};
but rather, to what I currently use, which is this:
C.)
var Blah = new Class({
extends: BaseClass,
new: function() {},
a: function() {},
b: function() {},
// also
static: { staticA: 123 }
});
There's no question that the object literal pattern A is superior to B. But for me, that's a false comparison. It's got to be better than C (or another similar class API), or I won't use it.
What if I'd like to use the goodies from A (like private named properties and super references), but I want the API from C? In my review I try to make that work, with only limited success. That's what I mean when I say that these features don't provide a foundation for building higher level class abstractions.
As far as I can tell, there are two ways out of this dilemma:
1.) Allow the RHS of <| to accept a non-literal value (I noticed this possibility under the Comments and Rationales section of the proto_operator strawman). This would allow me to set up the prototype chain (on a cloned version of the input argument) from within my class library, instead of outside of it.
2.) Add declarative class syntax to the language so that I can ditch the class library.
I hope that makes sense. Please let me know if something needs to be clarified.
Oh yeah...
First, "super delegation" is not a "class concept", rather it is a
fundamental concept of both inheritance or delegation based computational models. Look that the self language. You will find that it has super
You can definitely school me on languages any time, Allen! I'll have to research Self. And I'll probably give in on this point ; )
Kevin Smith wrote:
var Blah = BaseClass <| function(){} Blah.prototype.{ a: function(){}, b: function(){} }
The point I'm trying to drive home is that the above is no better (worse, in fact) from a user experience standpoint, than what I have now. To be blunt, I would not use the above construction - my library's API is better.
If this would be in the language, would you buy it instead?
BaseClass <| function Blah () { ... } class { // or "is class" if you like it more a() {}, b() {} }
var Blah = BaseClass <| function(){} Blah.prototype.{ a: function(){}, b: function(){} }
let Blah = BaseClass <| function() { }.prototype.( a() {}, b() {} }.constructor.{};
-
I don't like the imperative approximations of a declarative API. Since we're designing new language features, approximations should not be necessary.
There have been suggestions to interpret '<|' declaratively, and to avoid the special case for function rhs, ie, to interpret (p <| obj) simply as a structural representation of an object with un-nameable '[[prototype]]' field containing p.
What were the arguments against this?
-
JS claims to be prototype-based but the 'prototype'-field really points to shared traits, while the actual cloning of prototypes into objects happens in the constructor functions. A deviation from Self (if I recall that correctly), it is a source of confusion.
If we combine the two points (cloning happens in the constructor, <| declares 'prototype' field), we get something like:
var Blah = function(){ return BaseClass <| { a: function(){}, b: function(){} } }
Claus
The example you gave produces a constructor that wraps a handful of instance method definitions along with several function declarations - which I'm arguing could just as easily be outside of that function declaration, but inside of an IIFE.
There are two issues here:
- “Private methods” could be moved outside to the module level (IIFE level if you wish).
This is true, but would necessitate passing the private state into these methods:
callListener
would needoptions
, which then propagates to an extra parameter oncallListenersForSync
andcallListenersForAsync
. Obviously this problem multiplies as you add more private state to the object, eventually ending up passing around a “secrets” object containing all your private state, which is a bundle of joy to program against. </sarcasm>
- “Public methods” cannot be moved outside to the prototype level.
If instead of
that.publish = function () { ... }
inside the constructor, I hadPublisher.prototype.publish = function () { ... }
outside the constructor, then the function body could not access the “private instance variables”normalListeners
andoneTimeListeners
, or the constructor parameteroptions
.The only way to allow a
Publisher.prototype.publish
method to access this per-instance state would be to un-encapsulate it, making it public so that they could be accessed as e.g.this._normalListeners
.This is what I mean when I say I eschew the prototypal pattern in favor of the closure pattern. All of my methods are instance methods, because they need access to private, encapsulated state.
Hope I'm not misunderstanding the issue, but have you considered using modules/namespacing to get modularity+privacy?
You could have a module with internals, imported into those modules that define subclasses with access to the private internals. If you are using closures to approximate modules, you could pass the internals to the closure defining the subclass instead of passing internals to every method.
Claus
Claus Reinke wrote:
var Blah = BaseClass <| function(){} Blah.prototype.{ a: function(){}, b: function(){} }
let Blah = BaseClass <| function() { }.prototype.( a() {}, b() {} }.constructor.{};
- I don't like the imperative approximations of a declarative API. Since we're designing new language features, approximations should not be necessary.
There have been suggestions to interpret '<|' declaratively, and to avoid the special case for function rhs, ie, to interpret (p <| obj) simply as a structural representation of an object with un-nameable '[[prototype]]' field containing p.
You mean ditching function RHS completely or just not having it to specially treat LHS being function as well?
What were the arguments against this?
- JS claims to be prototype-based but the 'prototype'-field really points to shared traits, while the actual cloning of prototypes into
It's probably too late to rename it to instanceTraits (I find the name 'prototype' unhappy, too)
objects happens in the constructor functions. A deviation from Self (if I recall that correctly), it is a source of confusion.
If we combine the two points (cloning happens in the constructor, <| declares 'prototype' field), we get something like:
var Blah = function(){ return BaseClass <| { a: function(){}, b: function(){} } }
I'd say no to this -- ES is classless, but not true prototype oriented. I would keep the constructor-function-as-the-class-and-factory-at-once. Even if it is not optimal, but I think it is the cornerstone of ES object model.
Claus
Herby
P.S.: I would not be against <| being much more declarative, even if you deny function RHSes from being <|-able (but why? function may want to have prototype other than Function.prototype, too; and not only for "class-inheritance"); but only if there would be other possibility to do the "class-inheritance", that, double chain (constructor->constructor as
well as prototype->prototype).
function Sub (params) extends Super { body }
for example.
- I don't like the imperative approximations of a declarative API. Since we're designing new language features, approximations should not be necessary.
There have been suggestions to interpret '<|' declaratively, and to avoid the special case for function rhs, ie, to interpret (p <| obj) simply as a structural representation of an object with un-nameable '[[prototype]]' field containing p.
You mean ditching function RHS completely or just not having it to specially treat LHS being function as well?
Neither. Currently, <| sets the [[prototype]] field of the RHS object, and -if the RHS is a function literal- also specifies the [[prototype]] field of objects generated from it. I suggest to remove that special case for function RHS.
What were the arguments against this?
- JS claims to be prototype-based but the 'prototype'-field really points to shared traits, while the actual cloning of prototypes into It's probably too late to rename it to instanceTraits (I find the name 'prototype' unhappy, too) objects happens in the constructor functions. A deviation from Self (if I recall that correctly), it is a source of confusion.
If we combine the two points (cloning happens in the constructor, <| declares 'prototype' field), we get something like:
var Blah = function(){ return BaseClass <| { a: function(){}, b: function(){} } }
I'd say no to this -- ES is classless, but not true prototype oriented. I would keep the constructor-function-as-the-class-and-factory-at-once. Even if it is not optimal, but I think it is the cornerstone of ES object model.
If I'm not mistaken, the code above is valid in the current <| proposal (one might want to add a level of indirection to share a and b).
I'm not changing the constructor function approach, but the <| proposal makes it possible to specify the object [[prototype]] directly, without the indirection through the constructor function 'prototype'.
If working with functions wasn't so cumbersome in JS, and if <| wasn't limited to literal RHS (by making a shallow copy of the RHS, if necessary), the [[prototype]] setting could easily be abstracted out of the constructor, by function composition, without having to special-case function RHS in <|:
function compose(f,g) { .. } function extend(proto,constr) { return compose(function(obj) { return proto <| obj }, constr) } var Blah = extend(BaseClass, function(){ return { a: function(){}, b: function(){} } })
Of course, this would raise the question of whether the pattern is simple enough for code analyzers and optimizers to detect.. But mostly, the special case in <| is an artifact of function manipulation being too inconvenient in JS, and of <| being limited to literals (which is an artifact of object cloning being considered tricky).
This reminds me a lot of old-style Lego vs more flexible construction sets: there were way too many pre-fabricated components in Lego, because the base set wasn’t flexible enough. As a Lego kid, I was envious of other construction kits that had no built-in hurdles against 3d or mobile structures.
Is our ideal a language with few, simple components, from which all features we want can be built, or a language with lots of non-simple prefab components for common use cases?
The latter can paper over limitations in the core language, and can be quite successful in spite of its awkwardness. If we want the former, we need to simplify components were we can, and remove any obstacles in the core language that gives rise to non-simple proposals.
Claus
Currently, <| sets the [[prototype]] field of the RHS object, and -if the RHS is a function literal- also specifies the [[prototype]] field of objects generated from it. I suggest to remove that special case for function RHS.
Ultimately, this is my suggestion as well. And for class construction, bite the bullet and come up with a declarative class syntax.
On Mar 17, 2012, at 12:18 PM, Kevin Smith <khs4473 at gmail.com> wrote:
Currently, <| sets the [[prototype]] field of the RHS object, and -if the RHS is a function literal- also specifies the [[prototype]] field of objects generated from it. I suggest to remove that special case for function RHS.
Ultimately, this is my suggestion as well. And for class construction, bite the bullet and come up with a declarative class syntax.
Agreed. Come hell or high water, we need that class syntax to come through. I don't know if I can bear the mocking from the JS naysayers when ES6 comes out with <| as a crutch for classes because we can't agree on something. Can we at least start by agreeing that something is better than nothing? Coffeescript classes are incredibly minimal, maps to JS cleanly, and has yet to result in a bunch of coffeescript class abstraction libraries. It also leaves the door open to potential additions in the future. Let's at least start by committing to have them. Maybe for everyone to feel comfortable, we need a simple proposal that we can call the "safety syntax" - something that we know can grow, that we would all probably like to see have more features, but that we can fall back on if something better don't happen. We can do it!
On Mar 16, 2012, at 11:42 PM, Kevin Smith wrote:
Hi Allen,
Thanks for taking the time to read and respond. I've read your reply in detail and I feel like it deserves a point-by-point response, but I think the following might get us on the same page more quickly.
The important point to consider is that I'm not comparing this:
A.)
let Blah = BaseClass <| function() { }.prototype.{ a() {} b() {} }.constructor.{};
to this:
B.)
function Blah() Blah.prototype = Object.create(BaseClass.prototype); Blah.prototype.a = function() {}; Blah.prototype.b = function() {};
but rather, to what I currently use, which is this:
C.)
var Blah = new Class({ extends: BaseClass, new: function() {}, a: function() {}, b: function() {}, // also static: { staticA: 123 } });
There's no question that the object literal pattern A is superior to B. But for me, that's a false comparison. It's got to be better than C (or another similar class API), or I won't use it.
Perhaps, not a "false" comparison but a different comparison...
While you wouldn't use A because it can't express the full semantics of C, that doesn't mean that many users who currently have to write B wouldn't prefer to be able to write A.
I don't believe that simply allowing a non-literal value as the RHS of <| would be an adequate solution to your problem (or at least to all similar problems) because all that <| really does is set the [Prototype]] property of a new object. Object.create already lets you do that and we have also talked about supporting Object.extend as an alternative way to express the same thing.
As an alternative you suggest that declarative class syntax would make your library unnecessary. However, I assume that the same caveat applies for that syntax as you are applying to the object literal pattern. You would only use the declarative syntax if it supported the full class semantics you have chosen to use for your library. For example, I note that your library seems to have support for either/or multiple inheritance or mixins (which isn't totally clear from the examples). I assume that you wouldn't use declarative syntax that did not have equivalent features but neither is likely to be in any class syntax that we can reach agreement on in time for ES.next.
What you are essentially doing with your library is defining an internal DSL for expressing definitions for classes that conform to your preferred semantics. The linguistic atoms of your DSL are ES expression elements. Some self contained expression elements, such as constants, function expressions with no free variables, and object/array literal containing such elements work pretty well as DSL atoms. Expression elements that are not self contain, such as function expressions that need to capture a reference to a private name or a pre-instantiated object whose [[Prototype]] is already set don't work as well. You are depending upon the ES language to "compile" the function expressions into runtime entities that you will directly use within the class abstractions your DSL constructs. However, in some cases you really need to intercede before that compilation takes place so your can remap the meaning of the function expression to match your semantics (eg, handling private state references where you define the implementation model of the state). There is currently no way for you to do this, other than preprocessing the ES source code).
Supporting internal DSL is an interesting use case but not one that is explicitly stated as a harmony goal. However, supporting library writers is a goal. In some ways, "what do you need to build a better DSL for defining your classes" is something that may be easer to address than "what class syntax and semantics would satisfy you and every other ES programmer and library/framework provider".
On Mar 17, 2012, at 8:39 AM, Claus Reinke wrote:
- I don't like the imperative approximations of a declarative API. Since we're designing new language features, approximations should not be necessary.
There have been suggestions to interpret '<|' declaratively, and to avoid the special case for function rhs, ie, to interpret (p <| obj) simply as a structural representation of an object with un-nameable '[[prototype]]' field containing p.
You mean ditching function RHS completely or just not having it to specially treat LHS being function as well?
Neither. Currently, <| sets the [[prototype]] field of the RHS object, and -if the RHS is a function literal- also specifies the [[prototype]] field of objects generated from it. I suggest to remove that special case for function RHS.
What were the arguments against this?
(function () {}) creates two object, not one. We have to set the [[Prototype]] of both objects to something. In deciding how to do this it is important to look at the role of each member of that object pair. The existing language defines a relationship between them that we need to respect. We also need to consider what is going to be least surprising to users given the most common usage patterns.
Consider
let F1 = function() {}; F1.foo = "foo"; F1.prototype.bar = "bar"
let F2 = Fi <| function() {};
will a ES programmer expect F2.foo be be "foo" or undefined. Understanding that <| defines the [[Prototype]] of the the function they should expect "foo".
What about (new F2).bar will they expect "bar" or undefined. This is probably not something that they have thought about before, but I'm pretty confident that they will be astonished if they get undefined instead of "bar"
This has nothing to do with classes. It is just basic prototypal inheritance and the semantics of the new operator and function expressions.
- JS claims to be prototype-based but the 'prototype'-field really points to shared traits, while the actual cloning of prototypes into It's probably too late to rename it to instanceTraits (I find the name 'prototype' unhappy, too) objects happens in the constructor functions. A deviation from Self (if I recall that correctly), it is a source of confusion.
If we combine the two points (cloning happens in the constructor, <| declares 'prototype' field), we get something like:
var Blah = function(){ return BaseClass <| { a: function(){}, b: function(){} } }
I'd say no to this -- ES is classless, but not true prototype oriented. I would keep the constructor-function-as-the-class-and-factory-at-once. Even if it is not optimal, but I think it is the cornerstone of ES object model.
If I'm not mistaken, the code above is valid in the current <| proposal (one might want to add a level of indirection to share a and b).
I'm not changing the constructor function approach, but the <| proposal makes it possible to specify the object [[prototype]] directly, without the indirection through the constructor function 'prototype'.
If working with functions wasn't so cumbersome in JS, and if <| wasn't limited to literal RHS (by making a shallow copy of the RHS, if necessary), the [[prototype]] setting could easily be abstracted out of the constructor, by function composition, without having to special-case function RHS in <|:
function compose(f,g) { .. } function extend(proto,constr) { return compose(function(obj) { return proto <| obj }, constr) } var Blah = extend(BaseClass, function(){ return { a: function(){}, b: function(){} } })
Of course, this would raise the question of whether the pattern is simple enough for code analyzers and optimizers to detect.. But mostly, the special case in <| is an artifact of function manipulation being too inconvenient in JS, and of <| being limited to literals (which is an artifact of object cloning being considered tricky).
Even more so it is a matter that array, function, and RegExp objects have special semantics ("internal methods") that are not obtained via prototype inheritance and those literal forms imply the use of those special semantics. If you want to clone object (and compose objects) you have to worry about the "internal methods" and whether or not you want them to propagate to the clones or what it would mean to compose objects with conflicting internal methods.
Restricting <| to literals on the RHS was done to keep it a tractable problems for Es6.
Claus Reinke wrote:
- I don't like the imperative approximations of a declarative API. Since we're designing new language features, approximations should not be necessary.
There have been suggestions to interpret '<|' declaratively, and to avoid the special case for function rhs, ie, to interpret (p <| obj) simply as a structural representation of an object with un-nameable '[[prototype]]' field containing p.
You mean ditching function RHS completely or just not having it to specially treat LHS being function as well?
Neither. Currently, <| sets the [[prototype]] field of the RHS object, and -if the RHS is a function literal- also specifies the [[prototype]] field of objects generated from it. I suggest to remove that special case for function RHS.
I did not spell it well. This is what I meant with "specially treat LHS being function as well". I do not like the "also specifies ... from it" part, but you omitted a little details -- it only sets it when LHS has prototype property... nevertheless, it's an ugly special case.
On the other hand, I want this functionality built into language - because this is how "classical" class hierarchies are meant to be in JS. BUt I do not want it be a special case of <| - it should only set [[Prototype]] of newly created object, imo. I already proposed in other place that it can be for example:
function Sub (params) extends Super { body }
but distinct syntax element.
Allen Wirfs-Brock wrote:
On Mar 17, 2012, at 8:39 AM, Claus Reinke wrote:
- I don't like the imperative approximations of a declarative API. Since we're designing new language features, approximations should not be necessary.
There have been suggestions to interpret '<|' declaratively, and to avoid the special case for function rhs, ie, to interpret (p<| obj) simply as a structural representation of an object with un-nameable '[[prototype]]' field containing p. You mean ditching function RHS completely or just not having it to specially treat LHS being function as well? Neither. Currently,<| sets the [[prototype]] field of the RHS object, and -if the RHS is a function literal- also specifies the [[prototype]] field of objects generated from it. I suggest to remove that special case for function RHS.
What were the arguments against this?
(function () {}) creates two object, not one. We have to set the [[Prototype]] of both objects to something. In deciding how to do this it is important to look at the role of each member of that object pair. The existing language defines a relationship between them that we need to respect. We also need to consider what is going to be least surprising to users given the most common usage patterns.
Consider
let F1 = function() {}; F1.foo = "foo"; F1.prototype.bar = "bar"
let F2 = Fi<| function() {};
will a ES programmer expect F2.foo be be "foo" or undefined. Understanding that<| defines the [[Prototype]] of the the function they should expect "foo".
What about (new F2).bar will they expect "bar" or undefined. This is probably not something that they have thought about before, but I'm pretty confident that they will be astonished if they get undefined instead of "bar"
This has nothing to do with classes. It is just basic prototypal inheritance and the semantics of the new operator and function expressions.
Well, seems convincing... but nevertheless, I see it as a strange special case... maybe good example is "bare"
function foo () {}
How would you write it using <| ?
If you do
Function <| function foo () {} // [[Prototype]] set right
you get wrong prototype.[[Prototype]]. If you write
Object <| function foo () {} // prototype.[[Prototype]] set right
you get wrong [[Prototype]].
So it has to do a lot with classes... anything taking 'new foo' into account is taking classes into account.
That's why I think <| should not set prototype.[[Prototype]] at all. Plain functions then can be written
Function <| function foo () {} // *
and double chain (classical class inheritance) can be provided by different syntax, for example one I hinted:
function Bar () extends Baz {}
Allen
Herby
- I know there is no real point in writing bare constructor functions using <| at all - but it illustrates for me the problem - it cannot embrace this trivial scenario, so ... it hints it may be problematic.
On Mar 17, 2012, at 1:20 PM, Herby Vojčík wrote:
Allen Wirfs-Brock wrote:
...
(function () {}) creates two object, not one. We have to set the [[Prototype]] of both objects to something. In deciding how to do this it is important to look at the role of each member of that object pair. The existing language defines a relationship between them that we need to respect. We also need to consider what is going to be least surprising to users given the most common usage patterns.
Consider
let F1 = function() {}; F1.foo = "foo"; F1.prototype.bar = "bar"
let F2 = Fi<| function() {};
will a ES programmer expect F2.foo be be "foo" or undefined. Understanding that<| defines the [[Prototype]] of the the function they should expect "foo".
What about (new F2).bar will they expect "bar" or undefined. This is probably not something that they have thought about before, but I'm pretty confident that they will be astonished if they get undefined instead of "bar"
This has nothing to do with classes. It is just basic prototypal inheritance and the semantics of the new operator and function expressions.
Well, seems convincing... but nevertheless, I see it as a strange special case... maybe good example is "bare"
function foo () {}
How would you write it using <| ?
If you do
Function <| function foo () {} // [[Prototype]] set right
you get wrong prototype.[[Prototype]]. If you write
Object <| function foo () {} // prototype.[[Prototype]] set right
you get wrong [[Prototype]].
You write it
Function.prototype <| function foo() {}
and since Function.prototype does not itself have a prototype property, according to my <| spec, the foo.prototype gets the default value for its [[Prototype]] which is Object.prototype. Just like plain vanilla function foo() {}
BTW, check out www.wirfs-brock.com/allen/wp-content/uploads/2011/01/Fundamental-ECMAScript-Objects.png
So it has to do a lot with classes... anything taking 'new foo' into account is taking classes into account.
That's why I think <| should not set prototype.[[Prototype]] at all. Plain functions then can be written
Function <| function foo () {} // *
and double chain (classical class inheritance) can be provided by different syntax, for example one I hinted:
function Bar () extends Baz {}
Allen
Herby
- I know there is no real point in writing bare constructor functions using <| at all - but it illustrates for me the problem - it cannot embrace this trivial scenario, so ... it hints it may be problematic.
But it does embrace it, so maybe not so problematic after all...
While you wouldn't use A because it can't express the full semantics of C, that doesn't mean that many users who currently have to write B wouldn't prefer to be able to write A.
Yes - many (most?) users will benefit from being able to write A. But the thrust of my argument is not that there is a net loss to users, but that there's a subtle shift in flexibility going on. See below.
I don't believe that simply allowing a non-literal value as the RHS of <| would be an adequate solution to your problem (or at least to all similar problems)
For the general case, I agree. I've abandoned this line of thought.
As an alternative you suggest that declarative class syntax would make your library unnecessary. However, I assume that the same caveat applies for that syntax as you are applying to the object literal pattern. You would only use the declarative syntax if it supported the full class semantics you have chosen to use for your library.
No. I'll be more than happy to use any sane declarative class syntax, even without mixins or other features.
I think the following analogy will help explain my position:
Imagine the underlying facilities for class-like abstraction are the engine of a car, and the programming interface for utilizing those abstractions are the body.
ES3 gave us a pretty decent engine, housed inside of a real clunker. But ES3 was flexible, so it gave me the tools to create a shiny new body and to move the engine into that body.
ES6 (so far) gives us an even better engine, and this time it's housed in a slightly better-looking body. It's not dented and rusty, but it ain't no hot rod either. But this time around, I'm not able to move that engine into my own custom body - not without giving up a bunch of new features.
The more I think about the problem, the more sure I am of the solution: a proper class syntax, and removing the special case of <| where the RHS is a function.
Allen Wirfs-Brock wrote:
On Mar 17, 2012, at 1:20 PM, Herby Vojčík wrote:
Allen Wirfs-Brock wrote:
This has nothing to do with classes. It is just basic prototypal inheritance and the semantics of the new operator and function expressions.
Well, seems convincing... but nevertheless, I see it as a strange special case... maybe good example is "bare"
function foo () {}
How would you write it using <| ?
If you do
Function <| function foo () {} // [[Prototype]] set right
you get wrong prototype.[[Prototype]]. If you write
Object <| function foo () {} // prototype.[[Prototype]] set right
you get wrong [[Prototype]].
You write it
Function.prototype <| function foo() {}
Yeah, my mistake... the one I did was wrong.
So it has to do a lot with classes... anything taking 'new foo' into account is taking classes into account.
...
Herby
- I know there is no real point in writing bare constructor functions using <| at all - but it illustrates for me the problem - it cannot embrace this trivial scenario, so ... it hints it may be problematic.
But it does embrace it, so maybe not so problematic after all...
Well, I did what I can to show it is problematic in general
x <| function () {}
case, where you simple want x as [[Prototype]] and generic .prototype, but I probably failed to show you that it will backfire in future (don't tell me "just don't have prototype property in x", if I get it via chain of calls passing argument around, maybe from library user, than I am not the owner of x).
You see this special-casing as a right semantics, based on that it is natural, least-suprise... but really, if it cares about new X, it is about classes, don't you see it (plain setting [[Prototype]] does not care)?
Neither. Currently, <| sets the [[prototype]] field of the RHS object, and -if the RHS is a function literal- also specifies the [[prototype]] field of objects generated from it. I suggest to remove that special case for function RHS.
(function () {}) creates two object, not one.
I'm not sure what you meant here. That is one (function) object, using 'new' on it creates one new object to be initialized. Perhaps you meant (p <| function(){}) creates two objects, the modified function and its prototype? I had indeed managed to overlook that for a while, so some of my earlier examples lacked a level of indirection.
We have to set the [[Prototype]] of both objects to something.
But why ever set both to the same thing? Actually, I find myself disagreeing even before this, at the make-two-objects stage - it needlessly overloads a useful operator (<|) to make it fit one particular use case.
Assume that <|_ is an operator representing the simple base case of <|, namely
lhs <|_ rhs
- create object from rhs as normal, but set [[Prototype]] to lhs
Then, instead of setting two properties in a pre-determined create-two-objects pattern
{ prototype: p } <| function(){}
we can write out which property we mean to set, either
function(){}.{ prototype: p <|_ {} }
or
{ prototype: p } <|_ function(){}
which isn't much longer, makes explicit what is going on, and leaves the proto operator simple and flexible.
If the order of operands is inconvenient, define an infix operator (or the best approximation thereof that JS can do):
function class(cls) { return { subclass:
function(constr){ return constr.{prototype: cls.prototype <|_ {} } }
} }
or, more readably, with some form of short expression functions
class(cls)=>{ subclass: (constr)=>constr.{prototype: cls.prototype <|_
{} } }
Using this, we can write
class( { prototype: p } ).subclass( function(){} )
There are several language shortcomings that get in the way
- proper infix operators need language change
- <| and <|_ are half operator (taking expression parameters) and half language construct (taking syntax phrases), so one cannot built abstractions over them (example: extending the class wrapper above to set the function [[Prototype]] as well)
- making <| and <|_ proper operators requires shallow cloning, which isn't available
It would be good to address these. Apart from that, I see no convincing arguments, so far, for overloading <|.
Arguments in favor of <|_ :
- has simpler semantics
- can emulate <|
- is invertible (could also be used for destructuring)
The latter point, in particular, would allow to describe prototypical inheritance without reference to [[Prototype]] or proto, entirely at the JS source level, using <|_.
In deciding how to do this it is important to look at the role of each member of that object pair. The existing language defines a relationship between them that we need to respect. We also need to consider what is going to be least surprising to users given the most common usage patterns.
Surprise depends on expectation. Since we're defining a new operator, expectation depends on how that operator is defined and promoted.
Consider
let F1 = function() {}; F1.foo = "foo"; F1.prototype.bar = "bar"
let F2 = F1 <| function() {};
will a ES programmer expect F2.foo be be "foo" or undefined. Understanding that <| defines the [[Prototype]] of the the function they should expect "foo".
Agreed.
What about (new F2).bar will they expect "bar" or undefined. This is probably not something that they have thought about before, but I'm pretty confident that they will be astonished if they get undefined instead of "bar"
Why should they, unless they are confused about Function's [[Prototype]] vs prototype? Of course, most JS coders are confused about that at some point of their learning curve, but <| would be the first operator to support that confusion.
Speaking for myself, I was sold on <| when I thought it was <|_, and I was astonished about the extra bits thrown into its spec.
Perhaps the extra convenience of <| will win, but I thought it useful to think about the language limitations that made the convenient/complex definition seem necessary to you.
Claus
(function () {}) creates two object, not one.
I'm not sure what you meant here. ..
the empty object for the function's .prototype currently seems to elude me in all its forms..
Claus
Rather than abuse your inbox, I published this as a blog entry:
blog.khs4473.com/2012/03/using-object-literals-as-classes.html
Summary: It has been suggested that extended object literals can be used to define class-like abstractions, and that consequently class syntax is not necessary. I analyze the implications from the point of view of a library developer.
Comments?