Approach of new Object methods in ES5
On Apr 16, 2010, at 5:28 AM, Dean Edwards wrote:
On 16 April 2010 13:13, Dmitry A. Soshnikov <dmitry.soshnikov at gmail.com
wrote:
I think that approach used in ECMA-262-5 for new object methods
contradicts ES nature.+1
The new API seems quite random. I hope that JavaScript is not
turning into PHP.
Ouch! I've seen JS called a lot of things, but them's fighting words :- P.
Can you be more precise about "random"?
Is the problem the variation in naming convention (keys vs.
getMySummerVacationWasLong), or prototype-based "instance" methods
until ES5, constructor-based "static" methods in ES5?
The design is not random but it did involve a committee, which in
particular accounts for some of the variation in style. This problem
(for which I do not have a solution) affected ES3 too.
The static methods are to avoid "breaking the web" by injecting even
non-enumerable names into every object -- including the global object
-- via Object.prototype. Such injection can easily break user-
controlled "object detection" done on colliding names.
It might take the sting out if there were a way to curry the leading | obj| parameter as |this| that was cheaper than
Object.defineProperty(Object.prototype, 'keys', {value: function ()
{ return Object.keys(this); }});
In SpiderMonkey we have "uncurried" |this| from the Array extras and
other prototype-based generic instance methods, to support
Array.map(arraylike, ...) instead of
Array.prototype.map.call(arraylike, ...). This was relatively easy to
automate in our implementation. it's reminiscent of how Python class
methods are reflected.
Perhaps something like this could be generalized to recover the
preferred instance-based method from the "static" method, at the
programmers discretion and with a concise opt-in expression of some
sort.
On Fri, Apr 16, 2010 at 2:28 PM, Dean Edwards <dean.edwards at gmail.com>wrote:
On 16 April 2010 13:13, Dmitry A. Soshnikov <dmitry.soshnikov at gmail.com> wrote:
I think that approach used in ECMA-262-5 for new object methods contradicts ES nature.
+1
The new API seems quite random. I hope that JavaScript is not turning into PHP.
Indeed. The inconsistency and "hacks" is what drives me away from PHP.
I would prefer going by version and having the programmer specify the version somehow. Perhaps like a directive.
Pros:
- You can preserve backwards compat without allowing the inconsistency issues that PHP suffers from.
- If you didn't add the directive, you probably didn't use any features for which you required a directive
Cons:
- Browsers would have to support multiple implementations to preserve backwards compat. This con might not be that big a deal looking at the release frequency of the ecma spec :)
- Coders would have to know about such a directive, they're not used to requesting a specific version
- What would be the "default" version when no version was requested...
I'm interested whether there are any other alternatives. I'm expecting this to be shot down, hard ;)
2010/4/16, Dmitry A. Soshnikov <dmitry.soshnikov at gmail.com>:
By the way, it is also petty that there's no ability to change prototype and there is only "get" function for that; proto extension in this case was better.
Especially when I want to change only [[Prototype]] and keep values of other internal properties and methods for that object.
And I have a question. Why ES5 give control on values of internal attributes? What will improve that? "Save" augmentation of built-in? Good design of JS libraries?
Instead of that I want just naming convention improvements + const
keyword as Mozilla implement.
For example:
myObj.CONSTANT = 3.14;
And I expect property CONSTANT
to have internat attributes
{DontDelete), {ReadOnly}. But I forgot, Crockford suggest upper case
only for global variables.
If they was added new loop, misconseptions of JS library authors will be gone away. For example if there is:
for own(var i in obj) { //do something with next own property }
And my last question is. Why do you change terminology in ES5? What was wrong with ES3 terminology?
On Fri, Apr 16, 2010 at 4:18 PM, Asen Bozhilov <asen.bozhilov at gmail.com>wrote:
2010/4/16, Dmitry A. Soshnikov <dmitry.soshnikov at gmail.com>:
And I have a question. Why ES5 give control on values of internal attributes? What will improve that? "Save" augmentation of built-in? Good design of JS libraries?
If there was some way to refer to the internal properties then it'd be much easier to create test cases and do introspective analysis on the state of the interpreter. Although I'd prefer some kind of special method for (at least) accessing them, maybe even a special method/mode because I'm still unsure myself whether this could have dangerous (security related) repercussions.
Then there'd be no more guessing or workarounds to check whether some property has certain attributes set or unset. You could just query them directly and verify their contents. It would make building a test suite of any kind much easier...
On Apr 16, 2010, at 7:17 AM, Peter van der Zee wrote:
On Fri, Apr 16, 2010 at 2:28 PM, Dean Edwards
<dean.edwards at gmail.com> wrote: On 16 April 2010 13:13, Dmitry A. Soshnikov <dmitry.soshnikov at gmail.comwrote:
I think that approach used in ECMA-262-5 for new object methods
contradicts ES nature.+1
The new API seems quite random. I hope that JavaScript is not
turning into PHP.Indeed. The inconsistency and "hacks" is what drives me away from PHP.
Let's leave PHP out if it, this is es-discuss and so far I've seen
zero evidence of relevant PHP experience.
I would prefer going by version and having the programmer specify
the version somehow. Perhaps like a directive.
No, this makes the Web brittle, besides requiring the onerous multiple-
versioned implementations (or an implementation with exponentially
explosive if statements guarding changes and hiding additions).
Authors would tend to version-lock their content, increasing the odds
of non-interoperation and failure to work at all on some browsers.
Web authors do not want to write multiple versions of their program.
Rather, they want to support browsers back to a certain vintage,and
use object detection to take fast paths and make use of better APIs
where possible, with graceful degradation.
(A minority position involves server-side user-agent sniffing to ship
different content depending on UA capabilities, with compilation (e.g.
GWT) helping automate the "lowering" to run on older browsers.)
Even more fundamentally, a version is just another parameter to get
wrong, especially on top of the APIs being object-detected or simply
used without detection. We've seen this in the past with
JavaScript1.1, JavaScript1.2, etc. At some point IE stopped respecting
the non-standard version suffix on the HTML4-deprecated "language"
attribute, forcing all browsers to do likewise.
RFC 4329 does specify a version parameter on application/javascript
and application/ecmascript, which MIME types it also specifies with
reference to ECMA-262. However, the values allowed for this version
parameter are unspecified by RFC 4329, and IIRC IE still ignores this
parameter (contrary to the explicit language of the RFC -- but no
doubt IE's behavior predates this RFC being written).
The TC39 committee has pursued two approaches to versioning:
-
Extending the core language's object model (APIs) where name
collision is less likely, especially in Object (not Object.prototype).
The larger w3c DOM, Web API, and now WebIDL worlds also try to extend
API objects without opt-in versioning. -
Extending the language's syntax with:
(a) No versioning required in the absence of new reserved identifiers,
since new syntax cannot break existing content.
(b) Opt-in versioning if new reserved identifiers (even though only
contextually reserved per ES5, i.e., allowed as property names) are
already in use as parameter or variable names in web content.
We have talked about elaborating (1) to have a "frame-level" (iframe,
window) version selection mechanism, so as to avoid name collisions. I
believe IE8 and above do something like this, based on some kind of
doctype or META tag targeting a browser by name (should target browser
rendering engine including JS engine, but never mind!).
Many of us browser vendors on the committee do not intend to ship
multiple JS engines, or festoon our code with if statements, so
whether a future standard will version the object model remains to be
seen.
ES4 since Waldemar's 1998-era JS2 work was concerned with the problem
of versioning APIs implied by (1) greatly, and not just adding
properties: deleting and redefining too. ES4 proposed namespaces (like
Common Lisp packages, IIRC) as the solution. But namespaces are out
for Harmony.
My hope is that we can avoid versioning the object model and instead
simply extend certain objects that we effectively "reserve to the
implementation" (as ANSI and then ISO C does with certain global
names, e.g. __foo and _BAR). Indeed Ajax library developers now
generally avoid extending standard prototypes, and it's plausible this
best-practice could be extended to the built-in constructor objects too.
Object detection is tried and true on the Web. It is not declarative,
which makes it less efficient and arguably more error-prone -- but it
fosters graceful degradation and reduces over-large version scope and
versionitis.
For (2) we had a sketch of an idea championed briefly by Hixie and I
believe Maciej for syntax error recovery that would allow new syntax,
generally of the "keyword (expression) { statements }" form, to be
added to the language.
This still has problems with new keywords, and with future-hostility
against regexp and other lexical grammar changes. If the idea is to
allow new constructs among the "statements", it would require full
lexing and parsing of the entire construct to match the closing }, and
this would freeze regexp and other lexical syntax, besides being not
as efficient as the hoped-for "discard tokens till balacing } is
found" idea that I recall from the initial proposal.
ES5 strict reserves 'let' and 'yield', but this won't get rid of the
uses of these names as identifiers in existing web content, which
motivated us to add them in JS1.7 (Firefox 2) only under opt-in (RFC
4329 style) versioning.
So, more work is needed on (2b), but I do not think being too clever
(e.g., trying to reserve let but unreserving it once it is seen in an
unambiguous non-declaration and non-let-expression context) is a good
idea.
On Apr 16, 2010, at 7:18 AM, Asen Bozhilov wrote:
2010/4/16, Dmitry A. Soshnikov <dmitry.soshnikov at gmail.com>:
By the way, it is also petty that there's no ability to change
prototype and there is only "get" function for that; proto extension in
this case was better.Especially when I want to change only [[Prototype]] and keep values of other internal properties and methods for that object.
Sorry, I missed this in Dmitry's post (skimmed while traveling), but
settable proto, apart from the object initialiser use case (i.e.,
on a new object not yet reachable, analogous to ES5's Object.create),
is a terrible idea.
I write this having designed and implemented settable proto over
12 years ago. At the time, unstratified metaprogramming APIs were
popular; if it was good enough for Python, why not for JS. But the
lack of stratification is a problem (consider JSON data with a key
"proto"). And worse, the mutability means implementations must
check for cyclic prototype chains in order to avoid ilooping.
Mutable proto also makes optimization harder, or simply defeats
optimizations at the price of some complexity in the engine.
Finally, mutating proto on an existing object may break non-
generic methods in the new prototype object, which cannot possibly
work on the receiver (direct) object whose proto is being set.
This is simply bad practice, a form of intentional type confusion, in
general.
If specific cases wouldn't care because prototype-based methods are
generic, setting proto on an already-initialized object still
smells like bad form, although I admit a Self-ish programmer would
want it and probably use it well. But JS is not Self.
And I have a question. Why ES5 give control on values of internal attributes? What will improve that? "Save" augmentation of built-in?
(You probably mean "Safe" here.)
Good design of JS libraries?
All of these, but mostly to enable higher-integrity abstractions,
including ones that mimic the built-in objects (including the DOM --
more generally, all the notorious "host objects"; see also harmony:proxies)
.
We do not want the built-in objects to have all the power, including
to make properties non-enumerable and non-configurable but writable
(more on const below). The JS authors should have this power too, so
the committee is not a bottleneck for innovation where such attribute
control is necessary for integrity, emulation, or some other valid
reason.
Instead of that I want just naming convention improvements +
const
keyword as Mozilla implement.
Mozilla's 'const' does not allow you to make non-writable, non-
configurable (without which, non-writable has zero integrity)
properties of objects you create.
Mozilla's 'const' has other issues, but these will be fixed in
Harmony. See strawman:obj_initialiser_const
for more on Harmony ideas for 'const' as property initialiser keyword.
So a future 'const' can help, but it does not give enough control for
emulations and other use-cases that need to control the enumerable
attribute as well as writable and configurable.
Then there is the object-level [[Extensible]] internal property,
which 'const' does not cover in a general way in any Harmony proposal
I have seen. One special form:
const foo() { ... }
would create a block-scoped non-writable binding, initialized by
hoisting to the top of the block, whose value is the frozen function
foo. This seems good, if a bit "DWIM" in doing several things at once.
For example:
myObj.CONSTANT = 3.14;
And I expect property
CONSTANT
to have internat attributes {DontDelete), {ReadOnly}. But I forgot, Crockford suggest upper case only for global variables.
We're not able to introduce an uppercase-means-constant semantic rule
at this point, it's too incompatible.
Conventions are just that, and without ES5's metaprogramming API they
are advisory only -- again no integrity guarantees.
If they was added new loop, misconseptions of JS library authors will be gone away. For example if there is:
for own(var i in obj) { //do something with next own property }
Not a bad idea! We've talked about it a bit in context of ECMA-357's
popular (and separable from "E4X") "for each (var i in obj) ..." loop.
But it did not fit in ES5 with its "no new syntax not supported by 3
of 4 browsers" guideline.
And my last question is. Why do you change terminology in ES5? What was wrong with ES3 terminology?
Allen can field this one.
I will note that there are some real improvements in ES5, in
particular to Chapter 10 which now uses declarative binding
environments. ES1-3's abuse of objects for scopes (again I'm to blame
for doing so in JS in1995, economizing on objects needed to implement
the language in a big hurry) was a bug, not a feature.
On Apr 16, 2010, at 8:43 AM, Brendan Eich wrote:
- Extending the language's syntax with:
(a) No versioning required in the absence of new reserved
identifiers, since new syntax cannot break existing content.
Of course I glossed over how new syntax could make invalid content
start parsing, unexpectedly. We don't generally cater to this unlikely
case (I've never seen it).
Also, obviously existing browsers at the time new syntax is
promulgated by one or more browsers, perhaps based only on draft specs
but in nightly/testing builds, will cause syntax errors in old browsers.
This problem has no good solution other than server-side user-agent
sniffing in order to ship different content to the old browsers, or
perhaps equivalent client-side sniffing and eval usage (280 North and
others are now compiling on the browser, not on the server side).
On Apr 16, 2010, at 7:18 AM, Asen Bozhilov wrote:
2010/4/16, Dmitry A. Soshnikov <dmitry.soshnikov at gmail.com>:
By the way, it is also petty that there's no ability to change
prototype and there is only "get" function for that; proto extension in
this case was better.Especially when I want to change only [[Prototype]] and keep values of other internal properties and methods for that object.
Sorry, I missed this in Dmitry's post (skimmed while traveling), but
settable proto, apart from the object initialiser use case (i.e.,
on a new object not yet reachable, analogous to ES5's Object.create),
is a terrible idea.
I write this having designed and implemented settable proto over
12 years ago. At the time, unstratified metaprogramming APIs were
popular; if it was good enough for Python, why not for JS. But the
lack of stratification is a problem (consider JSON data with a key
"proto"). And worse, the mutability means implementations must
check for cyclic prototype chains in order to avoid ilooping.
Mutable proto also makes optimization harder, or simply defeats
optimizations at the price of some complexity in the engine.
Finally, mutating proto on an existing object may break non-
generic methods in the new prototype object, which cannot possibly
work on the receiver (direct) object whose proto is being set.
This is simply bad practice, a form of intentional type confusion, in
general.
If specific cases wouldn't care because prototype-based methods are
generic, setting proto on an already-initialized object still
smells like bad form, although I admit a Self-ish programmer would
want it and probably use it well. But JS is not Self.
And I have a question. Why ES5 give control on values of internal attributes? What will improve that? "Save" augmentation of built-in?
(You probably mean "Safe" here.)
Good design of JS libraries?
All of these, but mostly to enable higher-integrity abstractions,
including ones that mimic the built-in objects (including the DOM --
more generally, all the notorious "host objects"; see also harmony:proxies)
.
We do not want the built-in objects to have all the power, including
to make properties non-enumerable and non-configurable but writable
(more on const below). The JS authors should have this power too, so
the committee is not a bottleneck for innovation where such attribute
control is necessary for integrity, emulation, or some other valid
reason.
Instead of that I want just naming convention improvements +
const
keyword as Mozilla implement.
Mozilla's 'const' does not allow you to make non-writable, non-
configurable (without which, non-writable has zero integrity)
properties of objects you create.
Mozilla's 'const' has other issues, but these will be fixed in
Harmony. See strawman:obj_initialiser_const
for more on Harmony ideas for 'const' as property initialiser keyword.
So a future 'const' can help, but it does not give enough control for
emulations and other use-cases that need to control the enumerable
attribute as well as writable and configurable.
Then there is the object-level [[Extensible]] internal property,
which 'const' does not cover in a general way in any Harmony proposal
I have seen. One special form:
const foo() { ... }
would create a block-scoped non-writable binding, initialized by
hoisting to the top of the block, whose value is the frozen function
foo. This seems good, if a bit "DWIM" in doing several things at once.
For example:
myObj.CONSTANT = 3.14;
And I expect property
CONSTANT
to have internat attributes {DontDelete), {ReadOnly}. But I forgot, Crockford suggest upper case only for global variables.
We're not able to introduce an uppercase-means-constant semantic rule
at this point, it's too incompatible.
Conventions are just that, and without ES5's metaprogramming API they
are advisory only -- again no integrity guarantees.
If they was added new loop, misconseptions of JS library authors will be gone away. For example if there is:
for own(var i in obj) { //do something with next own property }
Not a bad idea! We've talked about it a bit in context of ECMA-357's
popular (and separable from "E4X") "for each (var i in obj) ..." loop.
But it did not fit in ES5 with its "no new syntax not supported by 3
of 4 browsers" guideline.
And my last question is. Why do you change terminology in ES5? What was wrong with ES3 terminology?
Allen can field this one.
I will note that there are some real improvements in ES5, in
particular to Chapter 10 which now uses declarative binding
environments. ES1-3's abuse of objects for scopes (again I'm to blame
for doing so in JS in1995, economizing on objects needed to implement
the language in a big hurry) was a bug, not a feature.
On Fri, Apr 16, 2010 at 08:43, Brendan Eich <brendan at mozilla.com> wrote:
ES4 since Waldemar's 1998-era JS2 work was concerned with the problem of versioning APIs implied by (1) greatly, and not just adding properties: deleting and redefining too. ES4 proposed namespaces (like Common Lisp packages, IIRC) as the solution. But namespaces are out for Harmony.
My hope is that we can avoid versioning the object model and instead simply extend certain objects that we effectively "reserve to the implementation" (as ANSI and then ISO C does with certain global names, e.g. __foo and _BAR). Indeed Ajax library developers now generally avoid extending standard prototypes, and it's plausible this best-practice could be extended to the built-in constructor objects too.
These are considered best practices because it breaks in the presence of other code outside your control. There is a reason why Prototype.js is popular. It allows people to write code in a more sane way, using methods that are bound to objects instead of global functions. If we had something like ES4 namespaces without all the issues (one can always dream) I'm sure that this best practice would be reversed.
On Apr 16, 2010, at 12:51 PM, Erik Arvidsson wrote:
On Fri, Apr 16, 2010 at 08:43, Brendan Eich <brendan at mozilla.com>
wrote:ES4 since Waldemar's 1998-era JS2 work was concerned with the
problem of versioning APIs implied by (1) greatly, and not just
adding properties: deleting and redefining too. ES4 proposed
namespaces (like Common Lisp packages, IIRC) as the solution. But
namespaces are out for Harmony.My hope is that we can avoid versioning the object model and instead
simply extend certain objects that we effectively "reserve to the
implementation" (as ANSI and then ISO C does with certain global
names, e.g. __foo and _BAR). Indeed Ajax library developers now
generally avoid extending standard prototypes, and it's plausible
this best-practice could be extended to the built-in constructor
objects too.These are considered best practices because it breaks in the
presence of other code outside your control. There is a reason why
Prototype.js is popular. It allows people to write code in a more
sane way, using methods that are bound to objects instead of global
functions. If we had something like ES4 namespaces without all the
issues (one can always dream) I'm sure that this best practice would
be reversed.
You're right -- I hear this from lots of developers. Ben Galbraith,
for example, says Prototype reduces keystrokes and he's right,
keystrokes aggregated over a long time do count. There is a
corresponding readability issue.
I was describing things as they are, not as they might be. But we do
not have a namespace solution.
However, ES5 does allow libraries to bind non-enumerable properties
of prototype objects. This is half of the solution, and if the
prototype in question is Object.prototype, or perhaps even then if the
name is obscure enough, a library might risk breaking object detection
on that name in the global object.
Another Harmony idea: strawman:names
for unforgeable property names not equated to any string. These
cannot collide, and with sugar to let them be used with . (not only in
computed property accesses using []), we may have a complete solution
for injecting new "names" into standard prototypes without breaking
existing code.
On Apr 16, 2010, at 12:58 PM, Brendan Eich wrote:
However, ES5 does allow libraries to bind non-enumerable
properties of prototype objects. This is half of the solution, and
if the prototype in question is Object.prototype, or perhaps even
Oops, missing "not": "... if the prototype in question is not
Object.prototype, or perhaps even then...."
then if the name is obscure enough, a library might risk breaking
object detection on that name in the global object.Another Harmony idea: strawman:names for unforgeable property names not equated to any string. These
cannot collide, and with sugar to let them be used with . (not only
in computed property accesses using []), we may have a complete
solution for injecting new "names" into standard prototypes without
breaking existing code.
Comments welcome on the names proposal. There are open issues at the
bottom, and the "private" keyword syntax is straw for sure, although
we don't have a better proposal AFAIK.
On Fri, Apr 16, 2010 at 09:06, Brendan Eich <brendan at mozilla.com> wrote:
On Apr 16, 2010, at 7:18 AM, Asen Bozhilov wrote:
2010/4/16, Dmitry A. Soshnikov <dmitry.soshnikov at gmail.com>:
By the way, it is also petty that there's no ability to change prototype
and there is only "get" function for that; proto extension in this case was better.
Especially when I want to change only [[Prototype]] and keep values of other internal properties and methods for that object.
Sorry, I missed this in Dmitry's post (skimmed while traveling), but settable proto, apart from the object initialiser use case (i.e., on a new object not yet reachable, analogous to ES5's Object.create), is a terrible idea.
I write this having designed and implemented settable proto over 12 years ago. At the time, unstratified metaprogramming APIs were popular; if it was good enough for Python, why not for JS. But the lack of stratification is a problem (consider JSON data with a key "proto"). And worse, the mutability means implementations must check for cyclic prototype chains in order to avoid ilooping.
Mutable proto also makes optimization harder, or simply defeats optimizations at the price of some complexity in the engine.
Finally, mutating proto on an existing object may break non-generic methods in the new prototype object, which cannot possibly work on the receiver (direct) object whose proto is being set. This is simply bad practice, a form of intentional type confusion, in general.
If specific cases wouldn't care because prototype-based methods are generic, setting proto on an already-initialized object still smells like bad form, although I admit a Self-ish programmer would want it and probably use it well. But JS is not Self.
Unfortunately there are use case (although limited) that cannot be solved without a mutable proto. Extending built classes is one such use case.
function HelloElement() { var el = document.createElement('div'); el.proto = HelloElement.prototype; el.text = 'Hello'; return el; } HelloElement.prototype = { proto: HTMLDivElement.prototype, set text(text) { this.textContent = text; }, say: function() { alert('Hello'); } };
document.body.appendChild(new HelloElement).say();
When we have this code sample I'd like to also point out that using proto in an object literal is much more user friendly and more efficient than using Object.create which is design for meta programming and not for users.
Compare the following two:
var obj = Object.create(myPrototype, getOwnPropertieswww.google.com/codesearch/p?hl=en#2U3RyB59VC0/trunk/site/traits/files/traits.js&q=getOwnProperties traits\.js&sa=N&cd=1&ct=rc&l=90 ({ ... }));
and:
var obj = { _proto: myPrototype, ... };
It is pretty clear that Object.create was never designed for ordinary, everyday use.
On Apr 16, 2010, at 1:11 PM, Erik Arvidsson wrote:
On Fri, Apr 16, 2010 at 09:06, Brendan Eich <brendan at mozilla.com>
wrote: ... settable proto, apart from the object initialiser use case
(i.e., on a new object not yet reachable, analogous to ES5's
Object.create), is a terrible idea.
[snip]
Unfortunately there are use case (although limited) that cannot be
solved without a mutable proto. Extending built classes is one
such use case.function HelloElement() { var el = document.createElement('div'); el.proto = HelloElement.prototype; el.text = 'Hello'; return el; } HelloElement.prototype = { proto: HTMLDivElement.prototype, set text(text) { this.textContent = text; }, say: function() { alert('Hello'); } };
document.body.appendChild(new HelloElement).say();
Does this work cross-browser? Gecko's DOM puts methods and WebIDL
attributes (getters/setters) on the prototype, which you chain to.
What happens if you access a standard HTMLDivElement method or getter
on (new HelloElement)?
Anyway, the Self-ish use case does arise in practice. It is not
something standard JS has ever catered to. I don't see us
standardizing mutable proto, or even read-only proto. We
should dig into this HelloElement case more before going further with
mutable proto, I think.
When we have this code sample I'd like to also point out that using
proto in an object literal is much more user friendly and more
efficient than using Object.create which is design for meta
programming and not for users.
Object.create is the standardized form of Crock's beget. It's not bad
for an API, albeit longer than b-e-g-e-t (but also less likely to
collide, although collide it did with TIBET's create method on Object).
Compare the following two:
var obj = Object.create(myPrototype, getOwnProperties({ ... }));
and:
var obj = { _proto: myPrototype, ... };
It is pretty clear that Object.create was never designed for
ordinary, everyday use.
Yes, I allowed for that as cited up top in this message -- proto
in an object initialiser is more usable, and it is useful (modulo
syntax and stratification lack). Note how the object being initialised
cannot yet be referenced, so no cycle detection is needed.
To stratify in some sense, or at least avoid name collisions, we would
need a new name for the property. The names proposal again comes to
mind: the implementation could create and expose a well-known Name
instance meaning what proto means now.
On Fri, Apr 16, 2010 at 10:28, Brendan Eich <brendan at mozilla.com> wrote:
Object.create is the standardized form of Crock's beget. It's not bad for an API, albeit longer than b-e-g-e-t (but also less likely to collide, although collide it did with TIBET's create method on Object).
My main issue with Object.create is that it takes an object where the values are property descriptors. Property descriptors are something I hope no one ever has to use in end user code. They are verbose and have the wrong defaults (I know we discussed this a lot and security over usability won).
On 16/04/2010 14:48, Brendan Eich wrote:
On Apr 16, 2010, at 5:28 AM, Dean Edwards wrote:
On 16 April 2010 13:13, Dmitry A. Soshnikov <dmitry.soshnikov at gmail.com> wrote:
I think that approach used in ECMA-262-5 for new object methods contradicts ES nature.
+1
The new API seems quite random. I hope that JavaScript is not turning into PHP.
Ouch! I've seen JS called a lot of things, but them's fighting words :-P.
Can you be more precise about "random"?
Is the problem the variation in naming convention (keys vs. getMySummerVacationWasLong)
Yes, that's the most grating part. The more variation there is in naming styles the more I have to remember. One of the things I always liked about JavaScript is that I didn't have to remember a lot of things.
The design is not random but it did involve a committee, which in particular accounts for some of the variation in style. This problem (for which I do not have a solution) affected ES3 too.
The variation isn't just in naming conventions though. The new static Object methods are effectively a new API and introduce a new paradigm in JS coding. I'm not against new things but I think that extending existing things is usually better. It all seems a little rushed somehow.
The static methods are to avoid "breaking the web" by injecting even non-enumerable names into every object -- including the global object -- via Object.prototype. Such injection can easily break user-controlled "object detection" done on colliding names.
I appreciate that. "Not breaking the web" seems to be the excuse that everyone trots out when they introduce a kludge. I'm sure the changes were necessary and I'll bow to my betters on this one.
It might take the sting out if there were a way to curry the leading |obj| parameter as |this| that was cheaper than
Object.defineProperty(Object.prototype, 'keys', {value: function () { return Object.keys(this); }});
Why isn't defineProperty an instance method? It seems strange that it is a static method.
In SpiderMonkey we have "uncurried" |this| from the Array extras and other prototype-based generic instance methods, to support Array.map(arraylike, ...) instead of Array.prototype.map.call(arraylike, ...). This was relatively easy to automate in our implementation. it's reminiscent of how Python class methods are reflected.
That's how I implemented generics in base2 too.
I don't want to moan too much about ES5 but I was pleased that someone posted something that echoed my thoughts.
2010/4/16, Brendan Eich <brendan at mozilla.com>:
On Apr 16, 2010, at 7:18 AM, Asen Bozhilov wrote:
Special thanks for your response!
Especially when I want to change only [[Prototype]] and keep values of other internal properties and methods for that object.
I write this having designed and implemented settable proto over 12 years ago. At the time, unstratified metaprogramming APIs were popular; if it was good enough for Python, why not for JS. But the lack of stratification is a problem (consider JSON data with a key "proto"). And worse, the mutability means implementations must check for cyclic prototype chains in order to avoid ilooping.
Mutable proto also makes optimization harder, or simply defeats optimizations at the price of some complexity in the engine.
I agree with your attentions. For example, I ever wonder how you treat
identifier with name __proto__
. Perhaps that depend on
implementation of Activation/Variable object.
(function () { var proto = {x : true}; print(x); })();
If VO is implemented as native object, that code will pollute Scope Chain and `x' will be resolve from VO[[Prototype]] during identifier resolution.
The static methods are to avoid "breaking the web" by injecting even
non-enumerable names into every object -- including the global object -- via Object.prototype. Such injection can easily break user-controlled "object detection" done on colliding names.
I appreciate that. "Not breaking the web" seems to be the excuse that everyone trots out when they introduce a kludge. I'm sure the changes were necessary and I'll bow to my betters on this one.
I don't see the ES5 Object API as a kludge at all.
On Fri, Apr 16, 2010 at 7:11 PM, Erik Arvidsson <erik.arvidsson at gmail.com>wrote:
On Fri, Apr 16, 2010 at 09:06, Brendan Eich <brendan at mozilla.com> wrote:
On Apr 16, 2010, at 7:18 AM, Asen Bozhilov wrote:
2010/4/16, Dmitry A. Soshnikov <dmitry.soshnikov at gmail.com>:
By the way, it is also petty that there's no ability to change prototype
and there is only "get" function for that; proto extension in this case was better.
Especially when I want to change only [[Prototype]] and keep values of other internal properties and methods for that object.
Sorry, I missed this in Dmitry's post (skimmed while traveling), but settable proto, apart from the object initialiser use case (i.e., on a new object not yet reachable, analogous to ES5's Object.create), is a terrible idea.
I write this having designed and implemented settable proto over 12 years ago. At the time, unstratified metaprogramming APIs were popular; if it was good enough for Python, why not for JS. But the lack of stratification is a problem (consider JSON data with a key "proto"). And worse, the mutability means implementations must check for cyclic prototype chains in order to avoid ilooping.
Mutable proto also makes optimization harder, or simply defeats optimizations at the price of some complexity in the engine.
Finally, mutating proto on an existing object may break non-generic methods in the new prototype object, which cannot possibly work on the receiver (direct) object whose proto is being set. This is simply bad practice, a form of intentional type confusion, in general.
If specific cases wouldn't care because prototype-based methods are generic, setting proto on an already-initialized object still smells like bad form, although I admit a Self-ish programmer would want it and probably use it well. But JS is not Self.
Unfortunately there are use case (although limited) that cannot be solved without a mutable proto. Extending built classes is one such use case.
function HelloElement() { var el = document.createElement('div'); el.proto = HelloElement.prototype; el.text = 'Hello'; return el; } HelloElement.prototype = { proto: HTMLDivElement.prototype, set text(text) { this.textContent = text; }, say: function() { alert('Hello'); } };
document.body.appendChild(new HelloElement).say();
When we have this code sample I'd like to also point out that using proto in an object literal is much more user friendly and more efficient than using Object.create which is design for meta programming and not for users.
Compare the following two:
var obj = Object.create(myPrototype, getOwnPropertieswww.google.com/codesearch/p?hl=en#2U3RyB59VC0/trunk/site/traits/files/traits.js&q=getOwnProperties traits\.js&sa=N&cd=1&ct=rc&l=90 ({ ... }));
and:
var obj = { _proto: myPrototype, ... };
It is pretty clear that Object.create was never designed for ordinary, everyday use.
-- erik
There is of course a very simple alternative to accessing "internal" properties like [[Prototype]] et.al. You could introduce a simple syntax using a new unused currently forbidden character into the language. To be honest, foo.bar#Prototype would be something that spring to mind. I know Mozilla uses it for sharp variables, but formally, the hash is not part of ES (yet). Of course other characters could be used here.
A special char that indicates attribute access, like the dot does for property access. This could not break any scripts...
Hello Brendan,
Friday, April 16, 2010, 5:48:34 PM, you wrote:
On Apr 16, 2010, at 5:28 AM, Dean Edwards wrote:
On 16 April 2010 13:13, Dmitry A. Soshnikov <dmitry.soshnikov at gmail.com
wrote:
I think that approach used in ECMA-262-5 for new object methods
contradicts ES nature.+1
The new API seems quite random. I hope that JavaScript is not
turning into PHP.
Ouch! I've seen JS called a lot of things, but them's fighting words :- P.
Without kidding -- that's similar to how PHP cannot manage how to name their global functions -- e.g. with or without underscores. And "Object.keys()" from some library which has no direct relation with ES (previous) spec and implementations looks similar.
But yeah, of course it's not about PHP, so won't mention it anymore.
Can you be more precise about "random"?
Is the problem the variation in naming convention (keys vs.
getMySummerVacationWasLong),
Yes, it is. Is it not so essential? The question even not in that "I should remember many things then" as Dean wrote (although, I see that Dean's position is similar to mine) but in having different syntactic (and what is more essential -- different ideological) approaches for the same semantically entity -- using methods related with object. That's about design, but not just about "I should remember more things".
or prototype-based "instance" methods until ES5, constructor-based "static" methods in ES5?
Yes, of course. Does it sound like "it is not enought to talk about it"?
The design is not random but it did involve a committee, which in
particular accounts for some of the variation in style. This problem
(for which I do not have a solution) affected ES3 too.
Yes, I understand that this question should go directly to committee.
The static methods are to avoid "breaking the web" by injecting even
non-enumerable names into every object -- including the global object -- via Object.prototype. Such injection can easily break user- controlled "object detection" done on colliding names.
That's the price (overloaded syntax with several ideolodies for the same entity) for users of several combined libraries.
It might take the sting out if there were a way to curry the leading | obj| parameter as |this| that was cheaper than
Object.defineProperty(Object.prototype, 'keys', {value: function ()
{ return Object.keys(this); }});
Yeah, true.
In SpiderMonkey we have "uncurried" |this| from the Array extras and
other prototype-based generic instance methods, to support
Array.map(arraylike, ...) instead of
Array.prototype.map.call(arraylike, ...). This was relatively easy to automate in our implementation. it's reminiscent of how Python class
methods are reflected.
Yes, but Python, having similar properties/methods resolution, in contrast with ES has different approach (with passing manual "self", and obj.foo() is just syntactic sugar for ObjClass.foo(obj)). There this ObjClass.foo(obj) is of course similar to Array.map(arraylike, ...).
But in ES we have to make aliases in this case -- to improve the code reuse and make it more elegant rather than write this long repeatable every time "Objec.- Object.- Object.-" with forced indentions if we need to call several operations.
Making aliases seems OK for me. In my local project in which I am sure and understand all consequences of augmentation of built-ins (prototypes and constructors, and therefore -- I can use language for full force) -- it will be the first thing I'll do -- provide convenient code reuse stylistics, making aliases for all these new methods and place them into "Object.prototype" with {enumerable: false}.
Regardless the meaning that to augment built-ins -- is a bad practice -- this is the best approach of plugged in functionality from the stylistics and code reuse viewpoint.
Of course I am talking only about own project where I'm free from 3rd-party libs naming collistions.
Providing so good mechanism (fully dynamic and mutable objects), you call augmentations as bad practice. Of course it is -- if we deal with environment we cannot trust (five 3rd-party libs are loaded + 10 scripts from untrusted sources). But if we're talking about own system in which we can control anything and understand completely how this system works -- it is very good approach.
But if you insist that it is a bad practice, it's easy to manage -- just forbid then augmentations of built-ins completely that programmers have no temptation to do this, like Python did. Yes, it turns on backward compatibilities again. But at some step in future a radical decision to stop support backward compat garbage and separate the new branch can be made. That relates also for this new "use strict", which will be supported all the time in the future for backward compats even if nobody will use any deprecated things -- but all of them will continue to pollute the course code with this "use strict".
But really, new plugged-in functionality looks more elegant in this way:
"foo".capitalize()
vs.
Ext.utils.Format.capitalize("foo");
Technically, this isn't a big difference how to write it (except algorithms of resolution):
YeahMySummerVacationWasLong.AndWeAvoidCollisions.capitalize("foo")
or "foo".capitalize()
But ideologically the difference is.
And imagine this appear many times. The same with new approach of Object.- Object.- Object.-. This is real example from ExtJS lib. But yeah, this is the price for using combination of 3rd-party libs and of course I understand it. But unfortunately, this became the price for the language syntax in a whole.
Perhaps something like this could be generalized to recover the
preferred instance-based method from the "static" method, at the
programmers discretion and with a concise opt-in expression of some
sort.
Yes, I think so.
Anyway, Brendan, thanks a lot for your clarifications. It was useful to listen about all these cases, including mutable proto and new "private" "Name"s approach.
Dmitry.
On Fri, Apr 16, 2010 at 8:58 PM, Brendan Eich <brendan at mozilla.com> wrote:
Another Harmony idea: strawman:names for unforgeable property names not equated to any string. These cannot collide, and with sugar to let them be used with . (not only in computed property accesses using []), we may have a complete solution for injecting new "names" into standard prototypes without breaking existing code.
Excuse me, seems I missed something. I thought first that you mentioned them in "private" viewpoint, but in this sentence you say that "names" will help to place standard methods/properties in built-ins prototypes avoiding naming collisions. But how will it look? Can you show an example, it is very interesting. If you e.g. place "getPrototype" "name" into "Object.prototype" how will it help to avoid collisions if user will place "string property" "getPrototype" into the "Object.prototype" (or just in global object)? How will it help for "object detection"?
Dmitry.
On Apr 17, 2010, at 7:25 AM, Dmitry Soshnikov wrote:
Excuse me, seems I missed something. I thought first that you
mentioned them in "private" viewpoint, but in this sentence you say
that "names" will help to place standard methods/properties in built- ins prototypes avoiding naming collisions. But how will it look? Can
you show an example, it is very interesting.
(function () { private getPrototypeName; Object.defineProperty(Object.prototype, getPrototypeName, {value: function () { return Object.getPrototypeOf(this); }});
// Your code using obj.getPrototype() freely here...
})();
You have to keep your hands on the getPrototypeName const in order to
use it as an identifier in obj.getPrototype(), of course. Thus the
module pattern to scope the private name. Publishing that name in a
global variable risks collisions too, but it's another possibility and
less likely to collide or break for-in enumeration than decorating
Object.prototype with a string-named property.
In short, the advantage is that you can use Prototype.js-style
instance methods, even on any object via Object.prototype delegation,
and not ES5-style static methods, while not not polluting
Object.prototype with string-named properties that could break some
other script.
If you e.g. place "getPrototype" "name" into "Object.prototype" how
will it help to avoid collisions if user will place "string
property" "getPrototype" into the "Object.prototype" (or just in
global object)? How will it help for "object detection"?
Note that there is no need to detect your privately-named
getPrototype. You just define it and use it. Again the aim is to avoid
breaking old code that tests for some other, possibly unrelated
getPrototype on any object that delegates to Object.prototype,
including the global object:
// This still works even in light of the above... if (! this.getPrototype) this.getPrototype = function () { return this.proto; }
Dave just pointed out that once the module pattern scope ends, the
privately-named Object.prototype.getPrototype is liable to be garbage
collected since it can't be referenced or enumerated.
So the module pattern (or real modules or lexical scope, also coming
in Harmony in some form) is as important as the Names proposal.
But scope isolation is not enough, since we are mutating a shared
object, Object.prototype, and the heap is not scoped. Suppose you
wanted to call a function from within your module that has monkey-
patched Object.prototype.getPrototype, and that function is in an
existing library that detects foo.getPrototype on some object foo.
On Apr 17, 2010, at 11:04 AM, Brendan Eich wrote:
On Apr 17, 2010, at 7:25 AM, Dmitry Soshnikov wrote:
Excuse me, seems I missed something. I thought first that you
mentioned them in "private" viewpoint, but in this sentence you say
that "names" will help to place standard methods/properties in
built-ins prototypes avoiding naming collisions. But how will it
look? Can you show an example, it is very interesting.(function () { private getPrototypeName; Object.defineProperty(Object.prototype, getPrototypeName,
Er, strike the -Name suffix:
private getPrototype; Object.defineProperty(Object.prototype, getPrototype,
/be (somewhat jetlagged at JSConf 2010)
Erik Arvidsson wrote:
Unfortunately there are use case (although limited) that cannot be solved without a mutable proto. Extending built classes is one such use case.
function HelloElement() { var el = document.createElement('div'); el.proto = HelloElement.prototype; el.text = 'Hello'; return el; } HelloElement.prototype = { proto: HTMLDivElement.prototype, set text(text) { this.textContent = text; }, say: function() { alert('Hello'); } };
document.body.appendChild(new HelloElement).say();
That design is used by some js libraries. Mutable proto allow us to change prototype chain of created object and safe internal methods of that object. For example:
function f() {} f.proto = { proto : Function.prototype };
f(); //still have [[Call]] method new f(); //still have [[Construct]] method
FuseJS use design like that for augment native objects. jdalton/fusejs The author use factories, which create native objects and mutate proto instead of augmentation directly created object. Lets suppose the follows factories:
var Factory = (function () { function object() { return {proto : object.prototype}; }
object.prototype.augment = function() {
//...
};
function array() {
var arr = Array.apply([], arguments);
arr.__proto__ = array.prototype;
return arr;
}
array.prototype = {
__proto__ : Array.prototype
};
return {
object : object,
array : array
};
})();
var obj = Factory.object(); /Augment created object with new properties/ obj.augment({ property1 : true, property2 : true, propertyN : true });
var arr = Factory.array(); /Augment created array with new properties/ arr.augment({ property1 : true, property2 : true, propertyN : true }); //Type Error
Cannot be shared properties by object.prototype and array.prototype, because they are not in same prototype chain. For generic methods you need copy reference in both object, which is not good. Of course that design is a little bit step forward instead of augmentation, which is used by some libraries.
Another benefit from mutable proto is opportunity to operate only with own properties. For example if you want shallow copy of some object you can use:
function clone(obj) { var copy = {proto : obj.proto}; obj.proto = {proto : null}; for (var i in obj) { copy[i] = obj[i]; } obj.proto = copy.proto; return obj; }
Hello Brendan,
Saturday, April 17, 2010, 7:04:52 PM, you wrote:
On Apr 17, 2010, at 7:25 AM, Dmitry Soshnikov wrote:
Excuse me, seems I missed something. I thought first that you
mentioned them in "private" viewpoint, but in this sentence you say
that "names" will help to place standard methods/properties in built- ins prototypes avoiding naming collisions. But how will it look? Can
you show an example, it is very interesting.
(function () { private getPrototypeName; Object.defineProperty(Object.prototype, getPrototypeName, {value: function () { return Object.getPrototypeOf(this); }});
// Your code using obj.getPrototype() freely here...
})();
This desugared view with enclosing anonymous function means that "getPrototype" name is available only in scope of of this function and when this scope ends, "getPrototype" name will be GC'ed (and related object too -- {value: ...}) and "Object.prototype" again won't have "getPrototype" name outside, right?
(Although, it's a bit strange, as it shouldn't be GC'ed as there is object related with this "name" object and vice-versa -- the "name" is still bound to this object {value: ...}. But, I don't know exactly about implementation).
I.e. we can use "obj.getPrototype() freely here..." -- only inside this object (in this case -- inside this function)? And outside of this object "obj.getPrototype()" can mean completely different thing -- with "string" or "name" key, right? And if already was declared the same "name"/"string" property, it isn't collide because our local "getPrototype" name has other identity, yeah?
But is there much sense in providing this "getPrototype" name for "Object.prototype" just inside our local library to make it convenient just for ourselvels. In this case we can provide some simple function.
But the main goal is to achieve the most convenient and elegant code reuse stylistic via placing this "getPrototype" directly into "Object.prototype" and make it available for /all users/ outside the library but not only for us inside the library.
You have to keep your hands on the getPrototypeName const in order to use it as an identifier in obj.getPrototype(), of course.
Unclear; you mean I should first define const "getPrototype" and then I can use it as "obj.getPrototype()"?
I.e.:
const getPrototype; private getPrototype;
?
Or I didn't understand correctly?
If you e.g. place "getPrototype" "name" into "Object.prototype" how
will it help to avoid collisions if user will place "string
property" "getPrototype" into the "Object.prototype" (or just in
global object)? How will it help for "object detection"?
Note that there is no need to detect your privately-named
getPrototype. You just define it and use it. Again the aim is to avoid breaking old code that tests for some other, possibly unrelated
getPrototype on any object that delegates to Object.prototype,
including the global object:
// This still works even in light of the above... if (! this.getPrototype) this.getPrototype = function () { return this.proto; }
Yes, I got it, thanks.
Dave just pointed out that once the module pattern scope ends, the
privately-named Object.prototype.getPrototype is liable to be garbage collected since it can't be referenced or enumerated.
As I mentioned, it seems strange, because "name" references {value: ...} object and vice-versa {value: ...} object reference "name" object property.
It isn't relates much, but (just for example): some other languages also allows to use not only strings as keys, but any other /hashable/ objects (using some calculated hash-value or identity for that). E.g. in the same Python:
z = {} # it's our "Object.prototype"
def foo():
def x():
pass
# use x function as a key of z dict
z[x] = 100
foo()
but it wasn't GC'ed and
function-key is still in z dict
although, we can't reference it via x key
print(z) # {<function x at 0x00BF3588>: 100}
But, I'm sure it will be implemented somehow correctly. And "name" object will be GC'ed and after that -- {value: ...} object as there is no references to it.
So the module pattern (or real modules or lexical scope, also coming
in Harmony in some form) is as important as the Names proposal.
By the way, is there any ability to test some Harmony implementation already? Maybe some part of it which currently is being implemented?
But scope isolation is not enough, since we are mutating a shared
object, Object.prototype, and the heap is not scoped. Suppose you
wanted to call a function from within your module that has monkey- patched Object.prototype.getPrototype, and that function is in an
existing library that detects foo.getPrototype on some object foo.
Yeah, and this function from the library will detect its own "foo.getPrototype" which it expects to detect but now our local "name" "getPrototype" which is also available for us as "foo.getPrototype" but with different meaning.
Thanks again, Dmitry.
On Apr 18, 2010, at 7:13 AM, Dmitry A. Soshnikov wrote:
This desugared view with enclosing anonymous function means that "getPrototype" name is available only in scope of of this function and when this scope ends, "getPrototype" name will be GC'ed (and related object too -- {value: ...})
That object may be GC'ed earlier, it's just a descriptor used to
define the prototype property.
and "Object.prototype" again won't have "getPrototype" name outside, right?
Right.
(Although, it's a bit strange, as it shouldn't be GC'ed as there is object related with this "name" object and vice-versa -- the "name" is still bound to this object {value: ...}. But, I don't know exactly about implementation).
See ES5 -- Object.defineProperty(Object.prototype, getPrototype,
{value: ...}) does not retain a reference to the {value: ...} object
after it returns. It merely consults it to see how to define the
property named by (in this case) the getPrototype Name object in
Object.prototype.
I.e. we can use "obj.getPrototype() freely here..." -- only inside
this object (in this case -- inside this function)?
Yes.
And outside of this object "obj.getPrototype()" can mean completely different thing --
with "string" or "name" key, right?
Right.
And if already was declared the same "name"/"string" property, it isn't collide because our local
"getPrototype" name has other identity, yeah?
Yes.
But is there much sense in providing this "getPrototype" name for "Object.prototype" just inside our local library to make it convenient just for ourselvels. In this case we can provide some simple function.
We could. But let's not go in circles: the premise is that
Prototype.js's style of extending built-in constructor's prototypes
(except for Object.prototype) is winning in terms of programmer
productivity, both producing (keystrokes) and consuming (readability).
I just confirmed that Prototype people do not plan to stop extending
built-in prototypes including Array, here at JSConf 2010. They are
moving away from extending DOM prototypes, I'm told, but not the
standard ones.
Anyway, this is more of a thought experiment about how to extend
standard prototypes without collisions. Recall the objection from
yourself and Dean Edwards to ES5's Object.keys, etc. At least Dean
averred that he preferred not only shorter names like keys to
getOwnPropertyNames, etc., but also prototype-based "instance methods"
to the Object-based "static methods".
But the main goal is to achieve the most convenient and elegant code reuse stylistic via placing this "getPrototype" directly into "Object.prototype" and make it available for /all users/ outside the library but not only for us inside the library.
If you want to add a string-named property to Object.prototype, you
can do it with ES5's Object.defineProperty -- no need for Name
objects. It still might break some object-detecting code that checks
for that string-equated name, though (again the premise for hauling in
Name objects to the discussion).
You have to keep your hands on the getPrototypeName const in order to use it as an identifier in obj.getPrototype(), of course.
Unclear; you mean I should first define const "getPrototype" and then I can use it as "obj.getPrototype()"?
I.e.:
const getPrototype; private getPrototype;
?
Or I didn't understand correctly?
In the names proposal, just 'private getPrototype;' would be enough.
So the module pattern (or real modules or lexical scope, also coming in Harmony in some form) is as important as the Names proposal.
By the way, is there any ability to test some Harmony implementation already? Maybe some part of it which currently is being implemented?
Some, so far mainly harmony:proxies for SpiderMonkey, see this bug.
An HTML attachment was scrubbed... URL: esdiscuss/attachments/20100418/c86e8c8e/attachment
On Fri, Apr 16, 2010 at 9:51 AM, Erik Arvidsson <erik.arvidsson at gmail.com> wrote:
On Fri, Apr 16, 2010 at 08:43, Brendan Eich <brendan at mozilla.com> wrote:
ES4 since Waldemar's 1998-era JS2 work was concerned with the problem of versioning APIs implied by (1) greatly, and not just adding properties: deleting and redefining too. ES4 proposed namespaces (like Common Lisp packages, IIRC) as the solution. But namespaces are out for Harmony. My hope is that we can avoid versioning the object model and instead simply extend certain objects that we effectively "reserve to the implementation" (as ANSI and then ISO C does with certain global names, e.g. __foo and _BAR). Indeed Ajax library developers now generally avoid extending standard prototypes, and it's plausible this best-practice could be extended to the built-in constructor objects too.
These are considered best practices because it breaks in the presence of other code outside your control. There is a reason why Prototype.js is popular. It allows people to write code in a more sane way,
Non sequitur.
On Fri, Apr 16, 2010 at 13:30, Tom Van Cutsem <tomvc at google.com> wrote:
The static methods are to avoid "breaking the web" by injecting even
non-enumerable names into every object -- including the global object -- via Object.prototype. Such injection can easily break user-controlled "object detection" done on colliding names.
I appreciate that. "Not breaking the web" seems to be the excuse that everyone trots out when they introduce a kludge. I'm sure the changes were necessary and I'll bow to my betters on this one.
I don't see the ES5 Object API as a kludge at all. From a metaprogramming/reflection point-of-view, this API is very much in line with the Mirror design principles outlined by Gilad Bracha and Dave Ungar (see bracha.org/mirrors.pdf) The paper makes the case why you don't want to define all 'meta-level' operations on the objects themselves. This architecture was used successfully in Self and in JDI (the Java Debugger Interface).
The problem is just that parts of the ES5 Object API is useful outside of meta programming and for everyday use we need syntactic sugar or a better API. Passing a map of property descriptors to Object.create might be convenient for meta programming but for everyday programming it adds a lot of extra useless cruft.
On Sat, Apr 17, 2010 at 02:03, Dmitry A. Soshnikov < dmitry.soshnikov at gmail.com> wrote:
But if you insist that it is a bad practice, it's easy to manage -- just forbid then augmentations of built-ins completely that programmers have no temptation to do this, like Python did.
Please be careful about what you wish for ;-) There are strong forces that want to do this as well as make all objects frozen by default.
The problem is just that parts of the ES5 Object API is useful outside of meta programming and for everyday use we need syntactic sugar or a better API. Passing a map of property descriptors to Object.create might be convenient for meta programming but for everyday programming it adds a lot of extra useless cruft.
I agree. This frustration of the property descriptors 'getting in the way' in part shaped the design of our traits library: the Trait constructorwww.traitsjs.org/api.htmltransforms a record into
a property descriptor map, enabling you to use methods like Object.create without having to spell out a property descriptor map. The library also defines a method "Object.getOwnProperties" that will perform this transformation without trait semantics. But I'm aware of the fact that this is not really a solution to the problem you are addressing. It's still a painfully long name, and awkward to use if all you want to do is to create an object with a particular prototype.
My guess is that the committee would rather standardize a minimal, powerful API, delegating the task of defining the convenience functions to library writers. That opens up the possibility of experimentation with different designs. Perhaps people are upset with this API because these convenience functions have not yet been developed, let alone become widespread.
On Apr 19, 2010, at 3:20 PM, Erik Arvidsson wrote:
On Sat, Apr 17, 2010 at 02:03, Dmitry A. Soshnikov <dmitry.soshnikov at gmail.com
wrote: But if you insist that it is a bad practice, it's easy to manage --
just forbid then augmentations of built-ins completely that programmers have no temptation to do this, like Python did.Please be careful about what you wish for ;-) There are strong
forces that want to do this as well as make all objects frozen by
default.
This is never gonna happen in the default version of JS. I don't need
to be the dead body over which it will happen. Lots of people active
in TC39 and the community would rebel. It would be a bloodbath for the
freeze-ist side (if such a side exists).
To paraphase Charlton Heston - "From my cold, dead hands"...
Three
On Sat, Apr 17, 2010 at 12:34 PM, Asen Bozhilov <asen.bozhilov at gmail.com> wrote:
Erik Arvidsson wrote:
Unfortunately there are use case (although limited) that cannot be solved without a mutable proto. Extending built classes is one such use case.
function HelloElement() { var el = document.createElement('div'); el.proto = HelloElement.prototype; el.text = 'Hello'; return el; } HelloElement.prototype = { proto: HTMLDivElement.prototype, set text(text) { this.textContent = text; }, say: function() { alert('Hello'); } };
document.body.appendChild(new HelloElement).say();
That design is used by some js libraries. Mutable proto allow us to change prototype chain of created object and safe internal methods of that object. For example:
function f() {} f.proto = { proto : Function.prototype };
f(); //still have [[Call]] method new f(); //still have [[Construct]] method
FuseJS use design like that for augment native objects. jdalton/fusejs
They really go all-out with that, don't they?
But for what reason? I really don't see the point in all that. Then again, I don't see the point in parsing the userAgent string for MSIE, but they do that, too, and apparently something related to somebody else defining an attachEvent.
To me, that reminds me of the common question "How do I subclass array in javascript," to which the best answer is "don't." However it looks as if FuseJS is going to prove that it is possible to provide a different answer to that old question.
Garrett
On 17.04.2010 0:30, Tom Van Cutsem wrote:
The static methods are to avoid "breaking the web" by injecting even non-enumerable names into every object -- including the global object -- via Object.prototype. Such injection can easily break user-controlled "object detection" done on colliding names. I appreciate that. "Not breaking the web" seems to be the excuse that everyone trots out when they introduce a kludge. I'm sure the changes were necessary and I'll bow to my betters on this one.
I don't see the ES5 Object API as a kludge at all. From a metaprogramming/reflection point-of-view, this API is very much in line with the Mirror design principles outlined by Gilad Bracha and Dave Ungar (see bracha.org/mirrors.pdf) The paper makes the case why you don't want to define all 'meta-level' operations on the objects themselves. This architecture was used successfully in Self and in JDI (the Java Debugger Interface).
Yes, it is. As I wrote, stratification for "meta" and "app" levels was a good reason in document mentioned at the beginning of this thread. But indeed, Java's implementation of this question (with mirror reflections) is just one of several. And this separation line can be smoothed. E.g. getting value of dynamically formed property name -- is it a /meta/ or /app/ operation? For example, Python treats it as meta:
getattr(bar, "foo")
In contrast, ES has for that purpose bracket notation property accessor, which is also /meta/ operation from this viewpoint. That means it supports introspection /embedded into the syntax/ without additional long intermediate operations, which makes its usage very convenient.
Should ES remove bracket notation (or change its meaning)? Who said that? Java?
String representation -- is it a /meta/ or /app/ operation? Python also has special "str" function (callable class) which takes responsibility for that (actually, calling internal str method of an object).
But Java and ES treat it as object's method -- toString.
Getting /length/ of something -- is a meta or app operation? And so on, and so forth.
It might take the sting out if there were a way to curry the leading |obj| parameter as |this| that was cheaper than Object.defineProperty(Object.prototype, 'keys', {value: function () { return Object.keys(this); }}); Why isn't defineProperty an instance method? It seems strange that it is a static method.
Imagine what Java would look like if all methods defined on java.lang.Class were defined on java.lang.Object directly instead (and would use 'this' instead of requiring an instance to be passed to Method.invoke and Field.get). As you mentioned yourself: <quote>"One of the things I always liked about JavaScript is that I didn't have to remember a lot of things."</quote> The more stuff that's defined on objects by default, the more I have to remember not to define a method called 'defineProperty' in my application. Defining them 'outside of' the objects means most Javascript programmers don't need to bother with these methods at all.
/Introspection/:
"foo" in bar foo["bar" + "baz"]
for /each/ ( ... in ...) {...}
/Self-modification/:
foo.bar = function () {}; delete foo;
/Execution of dynamically generated code/:
Function("alert(ba" + (x ? "r" : "z") + ")")();
var foo = (function () { if (x) { return function () {alert(1);} } else { return function () {alert(2);} } })()();
And so on.
How will it look in Java with its "Method.invoke" and "Field.get"? If it will take 10 intermediate actions for that -- could we send to hell such JavaScript then?
Keystrokes matter, but you have to take into account the cognitive overhead of remembering all inherited method names from some global object as well.
Neither keystrokes, nor "I should remember much" matter here. Any good editor should support auto-complete and useful code snippets. This about ideology of a language. But, yeah, it is debatable what should be treated as /meta/ operation and should ES do it the way Java does
Hello,
I'd like to clarify the question about approach used in ECMA-262-5 regarding the code design for new methods of Object constructor.
I've read the section "Attribute Control API Design and Rationale" of "Proposed ECMAScript 3.1 Static Object Functions: Use Cases and Rationale" document: proposals:proposal_to_refocus_tc39-tg1.pdf
I of course understand that now it is already too late to speak about it, because specification is already published. But nevertheless, maybe there is some hope to change something in new versions.
I think that approach used in ECMA-262-5 for new object methods contradicts ES nature. Of course, cases mentioned in that section of the document are correct and variations with "propertyIsDeletable", "propertyMakeEnumerable" as well as using a bit vector for handling property attributes (the descriptor) looks long and worse. So with these points everything's OK, they go without saying and can be omitted.
The point from that list with separation of Meta and App levels is interesting. But from this viewpoint all other methods of objects can be treated as meta-methods (such as "hasOwnProperty", "getPrototypeOf" and even "toString"). As well as e.g. bracket notation for property accessors which allows to access dynamic property names -- which is also kind of meta programming.
Accepted rules for new object methods -- that they should be directly in "Object" constructor instead of "Object.prototype" led to that now we have three (!) syntactically different approaches for the same semantically entity -- using methods of objects. These are:
I understand that "Object.keys()" is a kind of backward compatibility. But with what? With some js-libraty (in particular case I guess with "Prototype.js")?
First, the design of this method contradicts approved approach with "get" prefix (the second bullet), such as e.g. "getOwnPropertyNames".
Secondly, that is more essential, as ECMAScript uses the same semantics for the dot and bracket notations of property accessors, "keys" name can be improper. Because ES doesn't separate "keys/array indexes/properties", here all of them are "properties".
Thirdly, if to leave this "keys" and other similar "getters", maybe there was a sense to make them as real getters then? For example, o.keys, just like a.length (where "o" and "a" -- Object and Array instances accordingly). For what that useless call expression in this non-ideological "Object.keys()" method? And it still seems strange that it is just a "backward compatibility" with some library but not with previous version of language. In Ruby from which this "keys" methods was borrowed to "Prototype.js" there is separation in semantics of bracket and dot notations -- so there are "keys" in this case, but not "properties". In ES, I repeat, there is no such discrimination, so this "Object.keys()" looks odd for ES in contrast with all other new methods with "get" prefix.
Regarding the second bullet, all that "getters" (which are functions) also could be made as real getters:
o.ownProperties; // instead of Object.getOwnPropertyNames(o)
or "o.ownPropertyNames" if confusing that "property" isn't just a name, but property object with its descriptor. But this case isn't so essential, as is following.
Look at this (trivial, non-real, abstract) example:
var o = Object.freeze( Object.seel( Object.defineProperties( Object.create(proto), properties ) ) );
And compare it with the following one:
var o = Object.create(proto) .defineProperties(properties) .seel() .freeze();
I know that we can define properties together with "Object.create", repeat -- this example is abstract. But it is obvious that the first example is harder to read and this repetition of "Object. - Object. - Object." with forced indention looks (in my opinion) worse.
Of course, we can provide own aliases for all that new methods and place them in "Object.prototype", like e.g. this one: gist.github.com/367080 But this is just a compromise. Although, I prefer to use it in own projects improving the code reuse and avoiding this long repetitions of "Object. Object. Object." and similar. You talked about "long lines" in code when thinking about "propertyMakeEnumerable", but using this approach you provide also that long lines forcing to use this useless repetition of "Object." every time. Yeah, "propertyIsEnumerable" can be of course removed in some future (more radical) version, which will leave all old approaches (as Python did for 3.* version where some features are completely incomparable with 2.* versions -- and this is a good step for progress and do not carry all the time this old stuff).
Separating of "meta" and "app" of course is a good thing. But it is debatable what can be treated as "meta" level in this case. This cite from the mentioned above document looks really strange:
"/Object.prototype the methods would be part of the public interface of every application object in a program. As such, they need to be understood by every developer, not just library designers./"
Very strange cite. So, if I (and all) understand it correctly, all these new "meta" methods are just for library designers and should be (if possible) abstracted from "casual developer"? But of course it is not so, and all these methods will be used in every future code. Did you mean that user-defined object should contain only user-defined properties (with own invariants to be separated from other objects) and be like a simple hashtables without prototypes?
If any object can provide its string representation (using delegated "toString" from the prototype), why cannot it then get its "keys" using the same delegated approach? Why this action should be done by some "meta" (?) function? I think that "Object.prototype" /taking into account that in current API we can control [[Enumerable]] property/ is a good place for all base methods/properties of any object.
By the way, it is also petty that there's no ability to change prototype and there is only "get" function for that; proto extension in this case was better.
Of course I do not blame ES5 or something. And this letter isn't an appeal all to change everything, of course not. But I just want to know what other members think about that new /design/. Maybe I do not see some very useful consequence of this new approach? I think it is as important as technical parts. Will you continue to develop the language in this stylistics? If so, we should change our approaches and ideology of ES-programming too.
Dmitry.