extracting namespace from a property

Yuh-Ruey Chen maian330 at gmail.com
Tue Feb 27 15:26:15 PST 2007


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.

-Yuh-Ruey



More information about the Es4-discuss mailing list