extracting namespace from a property
You are right on top of this one. I've proposed a Name object (Jeff
suggested not using E4X's QName class) to reflect qualified
identifiers. This is necessary because what you propose is ambiguous
with respect to existing objects having properties with "::" in their
names (which has always been allowed; these would be accessed via []
of course).
Here's the guts of the proposal:
Problem Statement
The for-in loop combined with namespace-qualified names means we need
a Name class reflecting namespace-qualified identifiers of enumerable
properties:
let o = {x:1, 2:"two", '@funny':3}; for (let i in o) print(i, typeof i);
Per ES1-3, and required for backward compatibility, this script
should print
x string 2 string @funny string
Consider this code:
namespace n; let p = {n::m: 42}; for (let i in p) print(i, typeof i);
It would be impossible to index p.n::m by p[i] if i were “m” (if the
output of this loop were m string). We need something like the QName
class from E4X (ECMA-357). Jeff proposes we call it Name.
Proposed Solution
The Name class is defined as follows:
class Name { function Name(ns : Namespace, id : string) : qualifier = ns , identifier = id { }
intrinsic static function invoke(ns : Namespace, id : string) : Name new Name(ns, id);
public const qualifier : Namespace, identifier : string; }
An enumerating for-in loop or comprehension must reflect an
enumerable property name as a Name instance if the property was
defined with an explicit or default namespace other than public.
Otherwise, with no or a public namespace, enumerating for-in reflects
the name as a string per ES1-3 and existing JS implementations.
A property access of the form o[n] where n is of type Name looks for
a binding in the namespace n.qualifier whose unqualified name is
n.identifier.
The only detailing that remains is what Name.prototype.toString
should do. Current thinking is something like "[namespace public
n]::m" for the example in the Problem Statement. Comments welcome.
Brendan Eich wrote:
Jeff proposes we call it Name.
Since in ES4 all names are qualified names, why burden the language with a historical distinction that no longer exists?
Jd
-----Original Message----- From: es4-discuss-admin at mozilla.org [mailto:es4-discuss-admin at mozilla.org] On Behalf Of Brendan Eich Sent: Monday, February 05, 2007 2:33 PM To: Yuh-Ruey Chen Cc: es4-discuss at mozilla.org Subject: Re: extracting namespace from a property
You are right on top of this one. I've proposed a Name object (Jeff suggested not using E4X's QName class) to reflect qualified identifiers. This is necessary because what you propose is ambiguous with respect to existing objects having properties with "::" in their names (which has always been allowed; these would be accessed via [] of course).
Here's the guts of the proposal:
Problem Statement
The for-in loop combined with namespace-qualified names means we need a Name class reflecting namespace-qualified identifiers of enumerable properties:
let o = {x:1, 2:"two", '@funny':3}; for (let i in o) print(i, typeof i);
Per ES1-3, and required for backward compatibility, this script should print
x string 2 string @funny string
Consider this code:
namespace n; let p = {n::m: 42}; for (let i in p) print(i, typeof i);
It would be impossible to index p.n::m by p[i] if i were "m" (if the output of this loop were m string). We need something like the QName class from E4X (ECMA-357). Jeff proposes we call it Name. Proposed Solution
The Name class is defined as follows:
class Name { function Name(ns : Namespace, id : string) : qualifier = ns , identifier = id { }
intrinsic static function invoke(ns : Namespace, id : string) :
Name
Brendan Eich wrote:
intrinsic static function invoke(ns : Namespace, id : string) : Name new Name(ns, id);
invoke is called when Name is called as a function, right? I've seen this used before in Enumerator - just want to make sure.
An enumerating for-in loop or comprehension must reflect an
enumerable property name as a Name instance if the property was
defined with an explicit or default namespace other than public.
Otherwise, with no or a public namespace, enumerating for-in reflects
the name as a string per ES1-3 and existing JS implementations.
Although this helps, it would still be a hassle to have to check whether an enumerated property n is a Name or String. I can't do n.identifier if n is a String.
How about this: Each enumerated property n is always an instance of Name. If n doesn't have a qualifier, n.qualifier would be null, while n.identifier would be n's id. Thus, to get a property's id, n.identifier would work regardless of whether n has a qualifier. typeof(Name) would be "string". Name's prototype would be a String, so name instanceof String is true. These two requirements should be enough to satisfy ES3 compatibility.
The only detailing that remains is what Name.prototype.toString
should do. Current thinking is something like "[namespace public
n]::m" for the example in the Problem Statement. Comments welcome./be
First, if n.qualifier is null or undefined, toString() should just return n.identifier. Second, it doesn't really matter to me how n.qualifier would be represented in toString().
On Feb 5, 2007, at 7:05 PM, Yuh-Ruey Chen wrote:
Brendan Eich wrote:
intrinsic static function invoke(ns : Namespace, id : string) :
Name new Name(ns, id);invoke is called when Name is called as a function, right?
Yes.
An enumerating for-in loop or comprehension must reflect an enumerable property name as a Name instance if the property was defined with an explicit or default namespace other than public. Otherwise, with no or a public namespace, enumerating for-in reflects the name as a string per ES1-3 and existing JS implementations.
Although this helps, it would still be a hassle to have to check
whether an enumerated property n is a Name or String. I can't do
n.identifier if n is a String.
You have to use 'switch type' or equivalent.
How about this: Each enumerated property n is always an instance of Name. If n doesn't have a qualifier, n.qualifier would be null, while n.identifier would be n's id. Thus, to get a property's id,
n.identifier would work regardless of whether n has a qualifier. typeof(Name) would be "string". Name's prototype would be a String, so name instanceof String is true. These two requirements should be enough to satisfy ES3 compatibility.
That's far from clear, because ES3 mandates a primitive string, not a
String object. This will break any for-in loop that compares the
iterating variable to a saved value of that same variable (since ==
on Strings and other objects is identity, not string value comparison).
You make a good point about this identity being important:
'foo' in obj === Name(null, 'foo') in obj
The cost of a Name being cons'ed for every iteration of for-in is
also troubling, in optimization effort if not in runtime and space if
one argues that it could be optimized away. SpiderMonkey stores 31-
bit int ids in tagged machine words, not as strings -- never mind as
anything like Name instances.
It seems very counter-productive to introduce a new Name type. Surely it can only reduce E4X integration with ES4?
Peter
Peter Hall wrote:
It seems very counter-productive to introduce a new Name type. Surely it can only reduce E4X integration with ES4?
It seems even worse to me to overload QName to apply both places, since QNames also have a prefix property which makes no sense here.
(Rant: it seems even worse to have namespaces in XML expose prefixes at all, but that ship long since sailed. At the least, yay for E4X making <f:x xmlns:f="g"/> == <g:x xmlns:g="g"/>.)
Ok... I didn't realise that. In ActionScript 3, QName has only localName and uri...
What other implementations of E4X are there, and do they have a prefix property for QName?
Peter
Er, oops.
There's no prefix property, but there is a [[Prefix]] internal property, which implementations may optionally preserve. So if you use QName for |for (var qn in obj)| and preserve prefixes, you have to also manage some form of [[Prefix]]. The fact that it's not visible except by those methods which say they'll look at |name.[[Prefix]]| makes it slightly better, but only slightly, in my opinion. It's still added complexity and conflation of the same concept in two different contexts, which is the primary argument I'd make against it.
(Rant: it seems even worse to have namespaces in XML expose prefixes at all, but that ship long since sailed. At the least, yay for E4X making <f:x xmlns:f="g"/> == <g:x xmlns:g="g"/>.)
Prefixes are optional E4X feature that an implementation may choose not to support. They are only necessary to preserve the initial prefixes in the serialized XML. So perhaps E4X namespace can subclass the Name class?
, Igor
Igor Bukanov wrote:
Prefixes are optional E4X feature that an implementation may choose not to support.
Yeah, that's nice -- problem is that it really only works if everyone agrees not to expose prefixes or the spec mandates it, and since most other things preserve prefixes it's feature compat (everyone expects it or uses something else). I like the effort, but personally I think it was too late to try, a worse-is-better scenario.
They are only necessary to preserve the initial prefixes in the serialized XML. So perhaps E4X namespace can subclass the Name class?
I think I could go for that, although since you can't guarantee |n is Name| forall |for (var n in obj)|, I'm not sure doing so is really a useful gain.
On Feb 15, 2007, at 6:43 AM, Jeff Walden wrote:
Igor Bukanov wrote:
Prefixes are optional E4X feature that an implementation may choose not to support.
Yeah, that's nice -- problem is that it really only works if
everyone agrees not to expose prefixes or the spec mandates it, and
since most other things preserve prefixes it's feature compat
(everyone expects it or uses something else). I like the effort,
but personally I think it was too late to try, a worse-is-better
scenario.
Yes, this is a case where ECMA-357 followed wimpy ECMA-262 by
allowing too much to be decided by the implementation. That makes for
interop hell until there's a winner, when everyone else must follow
the leader. One could argue that it's better than the spec (written
based on one implementation) deciding in advance of adoption and
experience, but I believe that prefix preservation is what people
want from E4X. The spec should have just said so.
They are only necessary to preserve the initial prefixes in the serialized XML. So perhaps E4X namespace can subclass
(QName, not Namespace, I think Igor meant by "E4X namespace".)
the Name class?
I think I could go for that, although since you can't guarantee |n
is Name| forall |for (var n in obj)|, I'm not sure doing so is
really a useful gain.
If QName <: Name it still helps a bit. But yeah, Name is not related
to string type as discussed previously, so at least one type test
would be needed for uses of n in the loop body that did something
more involved, say parse n as a string, than what almost all such
loops do, obj[n].
Brendan Eich wrote:
On Feb 5, 2007, at 7:05 PM, Yuh-Ruey Chen wrote:
An enumerating for-in loop or comprehension must reflect an enumerable property name as a Name instance if the property was defined with an explicit or default namespace other than public. Otherwise, with no or a public namespace, enumerating for-in reflects the name as a string per ES1-3 and existing JS implementations.
Although this helps, it would still be a hassle to have to check
whether an enumerated property n is a Name or String. I can't do
n.identifier if n is a String.You have to use 'switch type' or equivalent.
That's hardly ideal. There are some use cases for processing keys, namely calling some string method on a key or passing a key to a function expecting a string, and any code doing that is likely going to break the moment a qualified property is added to an object. Consider:
for (k in o) { var prefix = k.substr(0, k.indexOf(':')); // do stuff }
which might be used if an author decided to use pseudo-namespaces (remember this is ES3 code). Both substr and indexOf are String methods, so it will only work if k is a string (either primitive or object) or is structurally compatible with String (at least with respect to substr() and indexOf() in this example). The proposed Name class doesn't have any of these methods, so the above code would cause an error. This is only one type of case, but I'm sure there are other use cases where a string method is called on a key.
How about this: Each enumerated property n is always an instance of Name. If n doesn't have a qualifier, n.qualifier would be null, while n.identifier would be n's id. Thus, to get a property's id,
n.identifier would work regardless of whether n has a qualifier. typeof(Name) would be "string". Name's prototype would be a String, so name instanceof String is true. These two requirements should be enough to satisfy ES3 compatibility.That's far from clear, because ES3 mandates a primitive string, not a
String object. This will break any for-in loop that compares the
iterating variable to a saved value of that same variable (since ==
on Strings and other objects is identity, not string value comparison).You make a good point about this identity being important:
'foo' in obj === Name(null, 'foo') in obj
The cost of a Name being cons'ed for every iteration of for-in is
also troubling, in optimization effort if not in runtime and space if
one argues that it could be optimized away. SpiderMonkey stores 31- bit int ids in tagged machine words, not as strings -- never mind as
anything like Name instances./be
Alright, here's another attempt :) I'm considering the fact that the difference between string keys and Name keys is only going to matter if some string method is called on a key or the key is passed to a function that expects a string.
Define a new type of primitive - I'll call it UnqualifiedKey. It is basically a string. The only difference is how it is handled when it's converted to an object. When an UnqualifiedKey primitive is converted to an object (typically as a temporary object that methods can act upon), the new Name(null, key) is returned. In terms of efficiency, this isn't much worse than what happens right now (temporary String objects being created).
Define another new type of primitive called QualifiedKey, which is basically a structure that includes a reference to a namespace object and the identifier string. typeof(qualified_key) == "string" (see (1) for rationale). When a QualifiedKey primitive is converted to an object, new Name(namespace, identifier) is returned.
Furthermore, Name will somehow contain all the String methods and properties, so that Name.stringprop is equivalent to Name.toString().stringprop. (If not for that toString() bit, Name could simply have String as its prototype to get all of String's methods.) Name's prototype would be String (even tho it has to redefine all the methods of String), so that name_obj instanceof String is true (see (1) for rationale again). Just like any other object, typeof(name_obj) == "object".
Define Name's toString() to be: (this.qualifier? (this.qualifier.toString() + "::" + identifier) : identifier).
Neither UnqualifiedKey nor QualifiedKey are objects, so a == b isn't identity equality for them. Instead overload the loose equality operator so that: - if a is a string and b is an UnqualifiedKey or QualifiedKey, then a == b iff a === String(b) (and vice versa) - if both a and b are either UnqualifiedKey or QualifiedKey, then a == b iff String(a) === String(b)
(1) There's probably some existing code out there like this:
function foo(s) { if (typeof s == "string" || s instanceof String) { // do something } else if (typeof s == "number" || s instanceof Number) { // do something } // etc. } for (k in obj) foo(s);
so typeof(k) should always be "string" and Object(k) instanceof String should always be true, regardless of whether s represents a qualified identifier or not.
On Feb 28, 2007, at 12:26 AM, Yuh-Ruey Chen wrote:
Brendan Eich wrote:
Although this helps, it would still be a hassle to have to check whether an enumerated property n is a Name or String. I can't do n.identifier if n is a String.
You have to use 'switch type' or equivalent.
That's hardly ideal.
Backward compatibility is that way, sometimes. But it trumps
incompatible idealism. :-/
There are some use cases for processing keys, namely calling some string method on a key or passing a key to a function expecting a string, and any code doing that is likely
going to break the moment a qualified property is added to an object. Consider:for (k in o) { var prefix = k.substr(0, k.indexOf(':'));
Such code is already broken by E4X, which extended the type of
property identifier to be a union with another arm (QName) already.
But perhaps E4X's spec authors made a mistake. Ok, moving on:
which might be used if an author decided to use pseudo-namespaces (remember this is ES3 code). Both substr and indexOf are String
methods, so it will only work if k is a string (either primitive or object)
or is structurally compatible with String (at least with respect to substr() and indexOf() in this example). The proposed Name class doesn't
have any of these methods, so the above code would cause an error. This is only one type of case, but I'm sure there are other use cases where a
string method is called on a key.
Igor suggested something similar in his last message: make String and
Name both compatible with a structural type. But we can't make String
<: {qualifier: Namespace, identifier: String} without polluting
String with 'qualifier' and 'identifier' properties. That seems as
bad, aesthetically, as anything you are trying to fix, although
operationally it might not break real-world code.
Define a new type of primitive - I'll call it UnqualifiedKey. It is basically a string. The only difference is how it is handled when it's converted to an object. When an UnqualifiedKey primitive is
converted to an object (typically as a temporary object that methods can act upon), the new Name(null, key) is returned. In terms of efficiency, this
isn't much worse than what happens right now (temporary String objects being created).
Adding primitives is the wrong way to go. It spreads complexity
around the built-in types' conversion methods, instead of leaving the
hassle for the (few, I claim) for-in loop writers who expect a string
id.
BTW, primitives are a problem still -- we had hoped to unify string
and String, boolean and Boolean, etc. (number types are different,
due to int uint double decimal being the "primitives" while Number is
the old wrapper class in ES1-3). Unification is looking too
incompatible, so we are trying to unify as much as possible and keep
compatibility. But more on that in a separate message.
Furthermore, Name will somehow contain all the String methods and properties, so that Name.stringprop is equivalent to Name.toString().stringprop.
Now that's a good idea, and it makes your counterexample above
(k.substr) work. I will make Name <: String in the name objects
proposal.
(1) There's probably some existing code out there like this:
function foo(s) { if (typeof s == "string" || s instanceof String) { // do something } else if (typeof s == "number" || s instanceof Number) { // do something } // etc. } for (k in obj) foo(s);
so typeof(k) should always be "string" and Object(k) instanceof String should always be true, regardless of whether s represents a qualified identifier or not.
Given Name <: String, it makes sense for (typeof n === "string")
given (n is Name).
I do not propose to add more primitive types. OK?
Thanks for keeping this conversation going.
Brendan Eich wrote:
There are some use cases for processing keys, namely calling some string method on a key or passing a key to a function expecting a string, and any code doing that is likely
going to break the moment a qualified property is added to an object. Consider:for (k in o) { var prefix = k.substr(0, k.indexOf(':'));
Such code is already broken by E4X, which extended the type of
property identifier to be a union with another arm (QName) already.
But perhaps E4X's spec authors made a mistake. Ok, moving on:which might be used if an author decided to use pseudo-namespaces (remember this is ES3 code). Both substr and indexOf are String
methods, so it will only work if k is a string (either primitive or object)
or is structurally compatible with String (at least with respect to substr() and indexOf() in this example). The proposed Name class doesn't
have any of these methods, so the above code would cause an error. This is only one type of case, but I'm sure there are other use cases where a
string method is called on a key.Igor suggested something similar in his last message: make String and
Name both compatible with a structural type. But we can't make String
<: {qualifier: Namespace, identifier: String} without polluting
String with 'qualifier' and 'identifier' properties. That seems as
bad, aesthetically, as anything you are trying to fix, although
operationally it might not break real-world code.
Yeah, that was going to be my first suggestion - adding qualifier and identifier to String as a hack.
But that does give me an alternative and much simpler idea: Overload the Name constructor and function to accept a single parameter, such that Name(k) == Name(null, k). This would allow code like this:
for (k in o) print(Name(k).identifier);
No need for a switch type statement, although that would probably be more efficient.
Define a new type of primitive - I'll call it UnqualifiedKey. It is basically a string. The only difference is how it is handled when it's converted to an object. When an UnqualifiedKey primitive is
converted to an object (typically as a temporary object that methods can act upon), the new Name(null, key) is returned. In terms of efficiency, this
isn't much worse than what happens right now (temporary String objects being created).Adding primitives is the wrong way to go. It spreads complexity
around the built-in types' conversion methods, instead of leaving the
hassle for the (few, I claim) for-in loop writers who expect a string
id.BTW, primitives are a problem still -- we had hoped to unify string
and String, boolean and Boolean, etc. (number types are different,
due to int uint double decimal being the "primitives" while Number is
the old wrapper class in ES1-3). Unification is looking too
incompatible, so we are trying to unify as much as possible and keep
compatibility. But more on that in a separate message.Furthermore, Name will somehow contain all the String methods and properties, so that Name.stringprop is equivalent to Name.toString().stringprop.
Now that's a good idea, and it makes your counterexample above
(k.substr) work. I will make Name <: String in the name objects
proposal.
Well as I mentioned, although Name would derive from String, it would still have to redefine all of String's methods. This implies that any method added to String's prototype would not automatically work with a Name instance. Unless the way Name delegates String methods via some noSuchMethod/resolve hook.
(1) There's probably some existing code out there like this:
function foo(s) { if (typeof s == "string" || s instanceof String) { // do something } else if (typeof s == "number" || s instanceof Number) { // do something } // etc. } for (k in obj) foo(s);
so typeof(k) should always be "string" and Object(k) instanceof String should always be true, regardless of whether s represents a qualified identifier or not.
Given Name <: String, it makes sense for (typeof n === "string")
given (n is Name).
Does this mean that in ES4 typeof Object(string) == "string" instead of "object" (and for other primitive wrappers)? Since Name instances are objects, typeof name should be "object", unless objects and their primitive counterparts have been unified with respect to typeof.
I do not propose to add more primitive types. OK?
I imagine those primitive types to be more of a language implementation construct, since they're basically strings and Names with special flags that determine how they're treated in the ToObject conversion.
Well whatever the case, I'd be satisfied with the solution I proposed above.
Thanks for keeping this conversation going.
/be
My pleasure :)
On Feb 28, 2007, at 6:28 AM, Yuh-Ruey Chen wrote:
But that does give me an alternative and much simpler idea:
Overload the Name constructor and function to accept a single parameter, such that Name(k) == Name(null, k). This would allow code like this:for (k in o) print(Name(k).identifier);
Ok, good idea -- updating the spec now. It is unusual to have
optional leading arguments, but it makes sense here, and it actually
corresponds to E4X's QName:
js> qn = new QName('local')
local js> qn.localName
local js> qn.uri
js> qn2 = new QName('uri', 'local')
uri::local js> qn2.uri
uri
No need for a switch type statement, although that would probably be more efficient.
It would, yeah.
Well as I mentioned, although Name would derive from String, it would still have to redefine all of String's methods.
Not so -- see ES1-3, where String.prototype methods except for
toString and valueOf are intentionally generic. They convert their
this parameter ToString and then operate on the result of that
internal type conversion call.
This implies that any method added to String's prototype would not automatically work with a Name instance. Unless the way Name delegates String methods via some noSuchMethod/resolve hook.
Generic methods on String.prototype automatically work, where "work"
is defined as operate on ToString(n), without hassle. If one adds a
specific String.prototype method that can't cope with Name, but
operates on its |this| parameter only if that parameter is exactly a
string instance already, then it will throw when called on Name --
which will tell the programmer to extend Name.prototype too (if we do
decide to make class Name be dynamic).
Given Name <: String, it makes sense for (typeof n === "string") given (n is Name).
Does this mean that in ES4 typeof Object(string) == "string"
instead of "object" (and for other primitive wrappers)?
That would have been the case if we had managed to merge string
primitive type with String object wrapper, but we can't, so what
happens instead is the same as today:
js> typeof Object('hi')
object js> Object('hi') instanceof String
true
Since Name instances are objects, typeof name should be "object", unless objects and their primitive counterparts have been unified with respect to typeof.
typeof new Name('hi') === "object", as you would expect.
We can't merge string and String, etc.
On Mar 1, 2007, at 6:19 AM, Yuh-Ruey Chen wrote:
Ah, I totally forgot about string methods being generic (AFAIK there's practically no code out there that has a constructor whose
prototype is String or has a String method as a property of a non-String object).
There is code that uses new String, however, so the ToString(this)
done by each generic method on entry (which results in a string
primitive) is important.
In standard mode, every class (except maybe host objects) are dynamic, right? Or at least would every builtin class is dynamic in standard
mode?
The built-in classes defined in ES3 section 15 are dynamic, but class
B extends A {} by default is not dynamic. You can have non-dynamic
subclasses of a dynamic class (necessary since Object is the base
class of all others and it's dynamic).
IIRC you are allowed to have a dynamic subclass of a non-dynamic
superclass (Jeff correct me if I'm wrong). dynamic is not inherited,
and applied to a class, it affects only mutability of instances
(whether one can add "expandos", i.e. whether the class "seals"
instances), again if my memory is correct. Others should correct me
or add more information as needed.
Standard vs. string mode does not change the default for a class that
lacks an explicit 'dynamic' qualifier.
typeof new Name('hi') === "object", as you would expect.
We can't merge string and String, etc.
/be
Hmm...that will be troublesome in the case where a key is passed to a function that tries to check if the passed argument is a string
only via typeof:function foo(x) { if (typeof x == 'string') { // if x is a Name, we won't get here } }
for (k in o) foo(k);
Of course, the function should have |x instanceof String|. But if the program was designed so that it guaranteed that it never passed String objects to foo, and foo is passed keys within a for-in loop, and that for-in loop was iterating over an object containing a qualified property, we run into backwards compatibility trouble.
I've changed the proposal to say that typeof n === "string"; we'll
see how that flies.
On the other hand, the |typeof null == 'null'| change is more
likely to break backwards compat, yet that's currently in ES4.
Concerns about compatibility is giving us pause even for this bug fix
to make typeof null != 'object' will break anyone who gratuitously
tests like so:
function isNull(a) { return typeof a == 'object' && !a; }
but I know of now such tests, and spidering various top 1000 web
sites finds cases that are currently assuming typeof null !=
'object'. Still the current proposal and spec says we are going to
attempt this fix, as you note.
I guess it depends on how much you're willing to break.
There's a fine line. If typeof n == 'string' is not helpful, then I
would prefer to make typeof n == 'object'. Spidering the visible web
can only produce counter-examples showing how we might help someone
with an incompatible "bug fix" -- it can't prove that we won't break
someone else.
New ES4 code would typically use the |is| operator anyway, and primitives and wrappers are unified under |is|, right?
Yes, string <: String, etc.
On Mar 1, 2007, at 12:07 PM, Brendan Eich wrote:
Standard vs. string mode does not change the default for a class
that lacks an explicit 'dynamic' qualifier.
s/string mode/strict mode/ of course
There's a fine line. If typeof n == 'string' is not helpful, then I
would prefer to make typeof n == 'object'. Spidering the visible
web can only produce counter-examples showing how we might help
someone with an incompatible "bug fix" -- it can't prove that we
won't break someone else.
Assuming spidering does not find evidence that the change will break
public web content, of course. You can't prove a negative. Still, we
should try to find for-in ~~> typeof id == 'string' dependencies.
I'll ask our spider-guy to start looking.
Brendan Eich wrote:
Well as I mentioned, although Name would derive from String, it would still have to redefine all of String's methods.
Not so -- see ES1-3, where String.prototype methods except for
toString and valueOf are intentionally generic. They convert their
this parameter ToString and then operate on the result of that
internal type conversion call.This implies that any method added to String's prototype would not automatically work with a Name instance. Unless the way Name delegates String methods via some noSuchMethod/resolve hook.
Generic methods on String.prototype automatically work, where "work"
is defined as operate on ToString(n), without hassle. If one adds a
specific String.prototype method that can't cope with Name, but
operates on its |this| parameter only if that parameter is exactly a
string instance already, then it will throw when called on Name --
which will tell the programmer to extend Name.prototype too (if we do
decide to make class Name be dynamic).
Ah, I totally forgot about string methods being generic (AFAIK there's practically no code out there that has a constructor whose prototype is String or has a String method as a property of a non-String object).
In standard mode, every class (except maybe host objects) are dynamic, right? Or at least would every builtin class is dynamic in standard mode?
Given Name <: String, it makes sense for (typeof n === "string") given (n is Name).
Does this mean that in ES4 typeof Object(string) == "string"
instead of "object" (and for other primitive wrappers)?That would have been the case if we had managed to merge string
primitive type with String object wrapper, but we can't, so what
happens instead is the same as today:js> typeof Object('hi') object js> Object('hi') instanceof String true
Since Name instances are objects, typeof name should be "object", unless objects and their primitive counterparts have been unified with respect to typeof.
typeof new Name('hi') === "object", as you would expect.
We can't merge string and String, etc.
/be
Hmm...that will be troublesome in the case where a key is passed to a function that tries to check if the passed argument is a string only via typeof:
function foo(x) { if (typeof x == 'string') { // if x is a Name, we won't get here } }
for (k in o) foo(k);
Of course, the function should have |x instanceof String|. But if the program was designed so that it guaranteed that it never passed String objects to foo, and foo is passed keys within a for-in loop, and that for-in loop was iterating over an object containing a qualified property, we run into backwards compatibility trouble.
On the other hand, the |typeof null == 'null'| change is more likely to break backwards compat, yet that's currently in ES4. I guess it depends on how much you're willing to break.
New ES4 code would typically use the |is| operator anyway, and primitives and wrappers are unified under |is|, right?
Brendan Eich wrote:
On Mar 1, 2007, at 6:19 AM, Yuh-Ruey Chen wrote:
In standard mode, every class (except maybe host objects) are dynamic, right? Or at least would every builtin class is dynamic in standard
mode?The built-in classes defined in ES3 section 15 are dynamic, but class
B extends A {} by default is not dynamic. You can have non-dynamic
subclasses of a dynamic class (necessary since Object is the base
class of all others and it's dynamic).IIRC you are allowed to have a dynamic subclass of a non-dynamic
superclass (Jeff correct me if I'm wrong). dynamic is not inherited,
and applied to a class, it affects only mutability of instances
(whether one can add "expandos", i.e. whether the class "seals"
instances), again if my memory is correct. Others should correct me
or add more information as needed.Standard vs. string mode does not change the default for a class that
lacks an explicit 'dynamic' qualifier.
So a classes' prototype is always going to be mutable?
In chapter 9, I see "Prototype objects are always instances of the dynamic class Object and therefore can always be extended by the addition of dynamic properties. Unlike with function closures which have a prototype property that is a variable and can be reset to another object, classes have a prototype that is read-only and so always points to the same object."
which to me says that although the prototype property is read-only for classes, the referenced prototype object can still be mutated.
I've changed the proposal to say that typeof n === "string"; we'll
see how that flies.I guess it depends on how much you're willing to break.
There's a fine line. If typeof n == 'string' is not helpful, then I
would prefer to make typeof n == 'object'. Spidering the visible web
can only produce counter-examples showing how we might help someone
with an incompatible "bug fix" -- it can't prove that we won't break
someone else
typeof name === "object" would be backwards compatible with ES3 if there are no enumerable qualified properties that are present on any builtin object. For ex, although the proposed iterator::get would be a property of Object, it's not enumerated and so no name object for it would be created in the default enumerator.
It only becomes an issue once a custom enumerable qualified property is added by the author. So maybe we should let typeof name === "object" and just warn developers to use |is| if they intend to enumerate over objects with enumerable qualified properties.
IIRC you are allowed to have a dynamic subclass of a non-dynamic superclass (Jeff correct me if I'm wrong). dynamic is not inherited, and applied to a class, it affects only mutability of instances (whether one can add "expandos", i.e. whether the class "seals" instances), again if my memory is correct. Others should correct me or add more information as needed.
I can't see it in the spec, but I was under the impression that, in general, dynamic is inherited, but Object is made a special exception to avoid making every object being dynamic. This is important because any code that relies on the fact that an object is dynamic (including the class itself) would break if the object turned out to be an instance of a subclass that didn't declare itself dynamic.
Peter
On Mar 1, 2007, at 7:01 PM, Yuh-Ruey Chen wrote:
So a classes' prototype is always going to be mutable?
Yes.
In chapter 9, I see "Prototype objects are always instances of the dynamic class Object and therefore can always be extended by the addition of dynamic properties. Unlike with function closures which
have
(Note to Francis: "function closures" here is wrong, it should be
"function objects".)
a prototype property that is a variable and can be reset to another object, classes have a prototype that is read-only and so always
points to the same object."which to me says that although the prototype property is read-only for classes, the referenced prototype object can still be mutated.
Right, when a property is described as read-only (or enumerable, or
whatever), it's the attribute of the property (slot attribute, if you
like), not the value (referent for an object type) that's being
constrained.
It only becomes an issue once a custom enumerable qualified
property is added by the author. So maybe we should let typeof name ===
"object" and just warn developers to use |is| if they intend to enumerate over objects with enumerable qualified properties.
Ok, that was my original position. Thinking about it, the only hazard
is if one is migrating a large body of JS1 code to JS2, adding
namespaced public properties to objects in the new code, but letting
such objects flow into old code's for-in loops that assert or require
typeof id == 'string'. We can make that a "fix as you find" case.
You may have gotten that impression from the old Netscape ES4 spec, circa 2003, which agrees with the scenario you describe.
We have not yet posted this to the wiki, but AFAIK, the current thinking is to have dynamic not inherited. This is the way ActionScript 3.0 implemented dynamic classes, i.e. the dynamic attribute is not inherited. Maybe someone else can chime in with the rationale as I don't remember (or I never knew).
Francis
On Mar 1, 2007, at 9:43 PM, Peter Hall wrote:
IIRC you are allowed to have a dynamic subclass of a non-dynamic superclass (Jeff correct me if I'm wrong). dynamic is not
inherited, and applied to a class, it affects only mutability of instances (whether one can add "expandos", i.e. whether the class "seals" instances), again if my memory is correct. Others should correct me or add more information as needed.I can't see it in the spec, but I was under the impression that, in general, dynamic is inherited, but Object is made a special exception to avoid making every object being dynamic. This is important because any code that relies on the fact that an object is dynamic (including the class itself) would break if the object turned out to be an instance of a subclass that didn't declare itself dynamic.
It would break only if the method insisted on adding an ad-hoc
property to |this|. That's really up to the cooperating sub- and
super-class implementations to work out. So it is not a contradiction
to have a non-dynamic subclass of a dynamic super-class.
-----Original Message----- From: es4-discuss-admin at mozilla.org [mailto:es4-discuss-admin at mozilla.org] On Behalf Of Francis Cheng Sent: Thursday, March 01, 2007 1:02 PM To: es4-discuss at mozilla.org Subject: RE: extracting namespace from a property
You may have gotten that impression from the old Netscape ES4 spec, circa 2003, which agrees with the scenario you describe.
We have not yet posted this to the wiki, but AFAIK, the current
thinking
is to have dynamic not inherited. This is the way ActionScript 3.0 implemented dynamic classes, i.e. the dynamic attribute is not inherited. Maybe someone else can chime in with the rationale as I
don't
remember (or I never knew).
Francis, You are correct -- the dynamic attribute is not inherited in AS3. As Brendan points out sealedness is an attribute of instance values, so at least that part of the meaning of 'dynamic' is independent of inheritance. The other meaning, in strict mode, that references through an object of a dynamic class are not checked, is a little more troublesome. But you have this problem anyway if you special case class Object as was done in AS2 and Netscape-JS2.
This is one of those design decisions that we debated at length and in the end chose the least of various imperfect alternatives, which included the two mentioned here and others involving additional attributes to separate out the various meanings of dynamic. In the end we chose to minimize special cases, and prefer sealedness to unsealedness. We didn't want to treat class Object specially, and we wanted to make the programmer ask for dynamic if he wants it.
-----Original Message----- From: es4-discuss-admin at mozilla.org [mailto:es4-discuss-admin at mozilla.org] On Behalf Of Peter Hall Sent: Thursday, March 01, 2007 12:44 PM To: Yuh-Ruey Chen Cc: Brendan Eich; es4-discuss at mozilla.org Subject: Re: extracting namespace from a property
IIRC you are allowed to have a dynamic subclass of a non-dynamic superclass (Jeff correct me if I'm wrong). dynamic is not
inherited,
and applied to a class, it affects only mutability of instances (whether one can add "expandos", i.e. whether the class "seals" instances), again if my memory is correct. Others should correct
me
or add more information as needed.
I can't see it in the spec, but I was under the impression that, in general, dynamic is inherited, but Object is made a special exception to avoid making every object being dynamic. This is important because any code that relies on the fact that an object is dynamic (including the class itself) would break if the object turned out to be an instance of a subclass that didn't declare itself dynamic.
Peter, Class objects are always unsealed for compatibility with ES3 constructor functions. Code that expects an instance to be unsealed needs to know what kind of instance it is. The only way to know for sure by looking at the type of a reference is if that type is of a class that is final and dynamic.
Hope that helps,
Peter, Class objects are always unsealed for compatibility with ES3 constructor functions. Code that expects an instance to be unsealed needs to know what kind of instance it is. The only way to know for sure by looking at the type of a reference is if that type is of a class that is final and dynamic.
Hope that helps, Jd
Thanks. I checked in AS3 and found, as you say, that it isn't inherited. A subtle change that I didn't notice between versions :-)
Peter
Is there any standard way to ascertain whether a property of a non-XML object is qualified by a namespace? This can be useful when enumerating over an object. I've looked at chapters 4 (fundamental concepts), 12 (namespaces), 14 (expressions), and 19 (native objects) and didn't find a clear answer.
In chapter 4, both readReference and writeReference delegate the responsibility of actually reading/setting values in traits.intrinsic::read and traits.intrinsic::write, respectively. Both of those are not defined anywhere, so I don't know how QNames are stored as properties.
In JS1.7, |obj.ns::prop| is equivalent to |obj['ns_uri::prop']| where ns's URI is 'ns_uri'. That allows something like this:
for (let qk in o1) { let ns_uri, k; let t = qk.split('::'); if (t.length == 1) { k = t[0]; } else { [ns_uri, k] = t; } print(ns_uri + '::' + k); }
Not only is this possibly non-standard, it's cumbersome and does not get the actual Namespace object.
Chapter 12 doesn't mention this issue at all.
Chapter 14 has production rules for qualifiers, but the evaluation of them makes use of internal functions and the internal Name datatype from chapter 4.
Chapter 19 doesn't mention this issue at all.
The meta-objects proposal has namespace() has a method of NominalType, but properties aren't types.