ES4 draft: Name

# Lars Hansen (18 years ago)

Name objects represent reflected namespace::identifier pairs. Here's the draft spec.

Comments welcome.

# Yuh-Ruey Chen (18 years ago)

Lars Hansen wrote:

  Name (a, b=…)

Description

The |Name| class object called as a function creates a new |Name| object by passing its arguments /a/ and /b/ to the |Name| constructor.

Returns

The |Name| class object called as a function returns a |Name| object.

Implementation

static meta function invoke(a, b=undefined): Name new Name(a, b);

If a is already a Name, shouldn't Name(a) simply return a? Not sure what the policy on this is, but if you've changed Map to work similarly, then I don't see why not here as well.

-Yuh-Ruey Chen

# Lars Hansen (18 years ago)

It depends on whether we care about object identity or not for immutable objects like Name that don't have special support from the language (eg in comparison operators). Useful question, though. I will ponder it.

# Lars Hansen (18 years ago)

Draft 2, changelog near the beginning.

Please note the OPEN ISSUES section, which names two fairly arbitrary designs in this proposal. Comments welcome.

# Waldemar Horwat (18 years ago)

public function Name(a, b=undefined) static meta function invoke(a, b=undefined): Name

It would be help the spec's readability if the formal parameters had more descriptive names than "a" and "b". Here it's not clear which one is which.

new Name( a, b=... )

Ah, I see why the formal parameters were called "a" and "b". I think that the design is too weird though -- one of the parameters ought to be a name and the other one ought to be an optional namespace. If I have the following:

var ns:Namespace = ... var id:string = ...

and don't know anything about the ...'s, then I currently can't just call:

n = new Name(ns, id);

Instead, I must use:

if (ns !== null) n = new Name(ns, id); else n = new Name(id, undefined);

The reason is that, under the current specification, new Name(null, x) requires x to be undefined (null is a Name).

[I'm assuming that the expression null is T is true for any nullable type T. If that's not the case, then there are different issues here.]

My preference would be to always have the identifier as the first parameter and the namespace as the optional second parameter. This will avoid hassles with values like null that can be either a namespace or a name.

Is there a compelling reason to treat numbers between 0 and 2^32-1 specially here? It adds complexity and I'm not sure what it's for.

Creating the ambiguity of whether Name values appear to be interned is likely to lead to trouble. For example, if I create a Map<Name, ...> and insert the result of Name(ns, "abc") into it then it's unclear whether the value Name(ns, "abc") will be present in the map. It's at the implementation's whim. We should specify it one way or another; my preference is to make these indistinguishable, which can be done either by interning them when creating them or by making === not distinguish them.

The |Name| constructor is implementation-dependent.

What implementation-dependent behavior does it have, other than the above? Shouldn't it be normatively specified in the spec?

What is analyzeArgs? It's defined but not used here.

Why does valueOf do a toString on the Name?

Waldemar
# Igor Bukanov (18 years ago)

The two-arguments Name constructor follow E4X where the corresponding constructor for QName has the form:

QName(namespace, id)

, Igor

# Lars Hansen (18 years ago)

-----Original Message----- From: Waldemar Horwat [mailto:waldemar at google.com] Sent: 11. mars 2008 19:17 To: Lars Hansen Cc: es4-discuss at mozilla.org Subject: Re: ES4 draft: Name

public function Name(a, b=undefined) static meta function invoke(a, b=undefined): Name

It would be help the spec's readability if the formal parameters had more descriptive names than "a" and "b". Here it's not clear which one is which.

new Name( a, b=... )

Ah, I see why the formal parameters were called "a" and "b".
I think that the design is too weird though -- one of the parameters ought to be a name and the other one ought to be an optional namespace. If I have the following:

var ns:Namespace = ... var id:string = ...

and don't know anything about the ...'s, then I currently can't just call:

n = new Name(ns, id);

Instead, I must use:

if (ns !== null) n = new Name(ns, id); else n = new Name(id, undefined);

The reason is that, under the current specification, new Name(null, x) requires x to be undefined (null is a Name).

Maybe that would be the better bug to fix -- allowing null explicitly as a namespace to cover that case.

[I'm assuming that the expression null is T is true for any nullable type T. If that's not the case, then there are different issues here.]

That is the case.

My preference would be to always have the identifier as the first parameter and the namespace as the optional second parameter. This will avoid hassles with values like null that can be either a namespace or a name.

As Igor pointed out that is incompatible with E4X, but that's hardly a good reason.

(What we could be looking at here is an artifact of specifying the language entirely with ES code, which is too weak to express the idea of multiple interfaces to a function.)

I think it's desirable to keep the current order for compatibility with existing designs (it's not like E4X sees no use). I'll try to clean up the constructor interface to be more robust / friendly.

Is there a compelling reason to treat numbers between 0 and 2^32-1 specially here? It adds complexity and I'm not sure what it's for.

It's because of EnumerableId, which treats values in that range specially. The thought behind the code the way it's written is that if one wishes to construct a Name, then the parameters must conform to those of EnumerableId: Name, string, or uint. Otherwise there's a type error.

However, this is a little too hard-nosed, and EnumerableId is changing anyway (if int and uint go away, as I expect they will), so I'll probably remove the checking here, and just add conversion to string for any argument at all.

Creating the ambiguity of whether Name values appear to be interned is likely to lead to trouble.

Yes. See separate thread that I spun off this one.

The |Name| constructor is implementation-dependent.

What implementation-dependent behavior does it have, other than the above? Shouldn't it be normatively specified in the spec?

It's implementation-defined whether the result of this is true:

x = new Name(a,b) y = new Name(a,b) x === y

but as discussed in that other thread, that problem needs to be solved, so the problem with "implementation-defined" here should go away.

What is analyzeArgs? It's defined but not used here.

"The helper function analyzeArgs, called by the Name constructor, takes the two values a and b that were passed to the Name constructor and returns an object containing a qualifier in the form of a namespace (which may be null) and an identifier in the form of a string. The qualifier and identifier are used to create the Name object."

Why does valueOf do a toString on the Name?

Entirely because the original spec (on the wiki) required it. I suspect this is the wrong design (and I think even Brendan, who wrote it up, questioned it, because it's left as an open issue on the wiki); the ActionScript implementation of the E4X "QName" valueOf method returns 'this'; that is probably the right behavior here as well.

# Brendan Eich (18 years ago)

On Mar 13, 2008, at 4:05 PM, Lars Hansen wrote:

Why does valueOf do a toString on the Name?

Entirely because the original spec (on the wiki) required it. I
suspect this is the wrong design (and I think even Brendan, who wrote it up, questioned it, because it's left as an open issue on the wiki);

The motivation there was to masquerade Name instances as strings to
ES3 code that did

for (var i in es4obj) { ... something assuming i is a string per ES3 here ... }

It was necessary to override valueOf and toString, since when this
proposal was written we believed that for-in loops over objects with
namespaced properties should iterate i over Name objects where
necessary to distinguish such properties from those identified by
names in no namespace.

We now are converging on hiding all namespaced properties from for-in
enumeration, which is great. This change does not eliminate the need
for Name objects in ES4, but it does mean that they may not need to
masquerade as Strings via subtyping, for backward compatibility. So
we should reconsider all the String hacks in Name.es in this light.

the ActionScript implementation of the E4X "QName" valueOf method returns 'this'; that is probably the right behavior here as well.

E4X's QName prototype valueOf is not specified, so that method is
delegated to Object.prototype.valueOf, which returns this.

# Lars Hansen (18 years ago)

Draft 3 of the spec for the Name class. Changelog near the beginning.

The main change is that Name extends Object (not String) and that ===, ==, !==, and != operate not on object identity or on string representation but directly on the (qualifier,identifier) pair. (A draft of an enumerability spec that justifies these changes is forthcoming.)

# Erik Arvidsson (18 years ago)

Should there be some informative text explaining that Names are immutable (non dynamic, final and only constant fields) so interning Name objects is something that might be useful for implementations?

2008/3/17 Lars Hansen <lhansen at adobe.com>:

# Lars Hansen (18 years ago)

I don't know if that's necessary; it follows directly from the properties you mention and the fact that == and === work by comparing the component fields. (By analogy, ES3 implementations already perform a fair amount of string interning but I don't recall that being discussed in the ES3 spec.)

# Erik Arvidsson (18 years ago)

Fair enough. On a related question, does the following allow removal of the field?

prototype function toString(this:Namespace) this.intrinsic::toString()

If not these should be changed to

prototype const function toString(this:Namespace) this.intrinsic::toString()

for Name and Namespace.

# Lars Hansen (18 years ago)

Prototype properties are generally just DontEnum. Why would the toString prototype function need to be const for Name and Namespace?

# Peter Hall (18 years ago)

Perhaps, something like this:

var n1 = new Name("foo", "bar"); var n2 = new Name("foo", "bar");

n1.toString = function(){.....}

An implementation might have reasonably shared the same instance for n1 and n2, but a user might be surprised that the new toString() function is now found on what he considered to be two separate instances.

Peter

# Lars Hansen (18 years ago)

That won't work; n1 and n2 are final and non-dynamic. And the prototype property is on the prototype, so you'd have to go

Name.prototype.toString = function () ...

but then you know what you're doing, hopefully.

# Lars Hansen (18 years ago)

Last call for comments.

# Mark S. Miller (18 years ago)

2008/3/20 Lars Hansen <lhansen at adobe.com>:

Last call for comments.

Since you're asking for a last call, I'll go on record as objecting to the introduction of Names, Namespaces, packages, and units. I think the current JavaScript practice --- of using lexical scoping, objects as records, package naming paths as dotted paths through records (e.g., Dojo & YUI) --- addresses most of these needs by patterns expressed using more basic primitives. If these new concepts were merely conveniences that could be explained as supporting and abstracting such patterns, as various Scheme module systems have done, that would be much less of a problem. (Even then, such adventures should not be designed in a standards committee.) However, as proposed as new primitive concepts, these don't pull their weight. I also object to classes on similar grounds, but we should probably keep that discussion separate.

The one use-case I can see for names and namespaces that isn't addressed adequately by existing patterns is expanding the property-name-space, to avoid accidental collisions on extensions to common abstractions. I note that Smalltalk has long faced this issue, and I know of at least three independent proposals for first-class selector names that were intended to address it. At least one of these were actually implemented. None were ever adopted by a significant user community. The problem with all of these is that, by introducing another layer of translation between the identifier the programmer wrote and the thing that's looked up, you have to introduce and explain all the mechanism for determining which translation to use in what context. (The T variant of Scheme is the only example I know where first-class selectors saw significant use. Though widely admired, no other Scheme or Lisp variants picked up on this feature.)

As I understand the history, proposed ES4 Names and Namespaces came from XML namespaces by way of the e4x adventure. However, there's a big difference. XML fully qualified names are founded in string identity (by comparing URLs). Their semantics can be explained by expansion to mangled strings as names ("<url>:local-name"). Proposed

ES4 Names and Namespaces are founded in object identity. I don't like XML-style names either. But mangled-strings-as-names don't present any novel serialization problem beyond those of simple-strings-as-names.

# Brendan Eich (18 years ago)

On Mar 20, 2008, at 5:29 PM, Mark S. Miller wrote:

As I understand the history, proposed ES4 Names and Namespaces came from XML namespaces by way of the e4x adventure.

Not so -- please see:

www.mozilla.org/js/language/old-js2/js20/core/namespaces.html

and talk to Waldemar Horwat about this long history.

# Lars Hansen (18 years ago)
# P T Withington (18 years ago)

On 2008-03-20, at 23:07 EDT, Lars Hansen wrote:

Names depend on object identity insofar as some namespace values are equal only to themselves. I don't know how serialization is a problem that figures into this, though clearly some namespaces values can't be printed and then reconstituted in the language.

Maybe the question is: "Why do we need un-interned Names"?

# Lars Hansen (18 years ago)

-----Original Message----- From: P T Withington [mailto:ptwithy at gmail.com] On Behalf Of P T Withington Sent: 21. mars 2008 04:58 To: Lars Hansen Cc: Mark S. Miller; es4-discuss at mozilla.org Subject: Re: ES4 draft: Name

On 2008-03-20, at 23:07 EDT, Lars Hansen wrote:

Names depend on object identity insofar as some namespace values are

equal only to themselves. I don't know how serialization is a

problem

that figures into this, though clearly some namespaces values can't

be

printed and then reconstituted in the language.

Maybe the question is: "Why do we need un-interned Names"?

Interning feels like it's beside the point but I think we're talking about the same thing: should the programmer be able to alway recreate any namespace value from its string representation?

Answering that in the negative, we've taken the approach that unforgeable namespaces is one of two mechanisms the language has for making properties and bindings inaccessible (the other being lexical scope).

I think we've had the discussion before: are namespaces only for organization or do they also provide some sort of integrity/hiding? We've gone with the latter; it introduces complexity in some corners (enumerability, reflection) but feels more useful in practice.

Forgeable (interned) namespaces are still available for serialization.

# Waldemar Horwat (18 years ago)

Mark S. Miller wrote:

The one use-case I can see for names and namespaces that isn't addressed adequately by existing patterns is expanding the property-name-space, to avoid accidental collisions on extensions to common abstractions. I note that Smalltalk has long faced this issue, and I know of at least three independent proposals for first-class selector names that were intended to address it. At least one of these were actually implemented. None were ever adopted by a significant user community. The problem with all of these is that, by introducing another layer of translation between the identifier the programmer wrote and the thing that's looked up, you have to introduce and explain all the mechanism for determining which translation to use in what context. (The T variant of Scheme is the only example I know where first-class selectors saw significant use. Though widely admired, no other Scheme or Lisp variants picked up on this feature.)

Really? Lisp is a great example of the benefit of namespaces. Namespaces are essential to Common Lisp. In my Netscape days I wrote the semantic engine for the reference implementation of ES4 in Common Lisp. Lisp namespaces were crucial to maintaining my sanity while structuring that program.

Waldemar
# Waldemar Horwat (18 years ago)

Lars Hansen wrote:

The operators |==| and |===| compare as equal two |Name| objects that are separately created from the same (qualifier,identifier) pair; similarly, the operators |!=| and |!==| compare as not equal two |Name| objects that are separately created from (qualifier,identifier) pairs.

This is a non-sequitur. I agree with the intent, but, as stated this would indicate that:

!(new Name(123) == new Name("123")) from the first clause

Worse, as written, both new Name("abc") == new Name("abc") and new Name("abc") != new Name("abc") would be true!

What you want to state instead is that |Name| objects are equal iff their qualifier and identifier properties are equal.

What happens if you call new Name() with either zero or three or more arguments?

var x:Name = new Name(my_namespace, "xyz"); var y:Name = Name(x);

Now x is as you'd expect, but y is a Name whose namespace is null and identifier is "MyNamespace::xyz" (or whatever toString does on x).

The relational operators are broken on Name objects. For example,

a <= b

is not equivalent to

a == b || a < b

For another example, you can have two Name objects for which none of

a < b, a == b, a > b

is true. This makes it difficult to sort these, put them into ordered containers, etc.

The counterexamples to these invariants are the very x and y from the prior example :-).

Waldemar
# Mark S. Miller (18 years ago)

On Fri, Mar 21, 2008 at 2:14 PM, Waldemar Horwat <waldemar at google.com> wrote:

Mark S. Miller wrote:

The one use-case I can see for names and namespaces that isn't addressed adequately by existing patterns is expanding the property-name-space, [...] (The T variant of Scheme is the only example I know where first-class selectors saw significant use. Though widely admired, no other Scheme or Lisp variants picked up on this feature.)

Really? Lisp is a great example of the benefit of namespaces. Namespaces are essential to Common Lisp. In my Netscape days I wrote the semantic engine for the reference implementation of ES4 in Common Lisp. Lisp namespaces were crucial to maintaining my sanity while structuring that program.

I'd forgotten about the Common Lisp package system. Unlike T and Smalltalk, Common Lisp doesn't really have objects with selector-based method dispatch. Instead, they overload a multimethod on the types of its arguments. The parts of a multimethod are gathered together in a strange fashion. But, IIUC, once gathered, a multimethod is looked like any lexical definition -- by looking up a textual identifier in a package to get a symbol, and then looking that symbol up in the normal lexical environment to get a location. I consider that to be an example of my previously explained use case: using an extra level of indirection prior to lexical environment lookup. This is an example of the package-like use case about which I wrote:

I think the current JavaScript practice --- of using lexical scoping, objects as records, package naming paths as dotted paths through records (e.g., Dojo & YUI) --- addresses most of these needs by patterns expressed using more basic primitives.

Multimethods do blur the line between lexical name lookup and selector-based method dispatch. This line is blurry enough that Common Lisp may indeed be an example or a counter-example, depending on how one their package system and their object system. Whereas Scheme was easily subset into the object capability language W7 mumble.net/~jar/pubs/secureos, attempts to secure Common

Lisp went nowhere. My sense has been www.eros-os.org/pipermail/e-lang/2005-April/010572.html that

Common Lisp's package system and its multimethod-based object system were the main impediments.

# Peter Hall (18 years ago)

Forgeable (interned) namespaces are still available for serialization.

How will those two treatments interact? If there is a mechanism for creating forgeable namespaces behind the scenes, can this be exposed to programs?

E4X needs forgeable namespaces, but I assume that comes under (de)serialization. AS3 currently has forgeable namespaces in general and I know of existing code that depends on that, but I don't know if that is relevant. Non-forgeable is definitely a stronger concept.

Peter