(Weak){Set|Map} subclassing

Allen Wirfs-Brock allen at wirfs-brock.com
Sat Dec 1 12:38:25 PST 2012

On Dec 1, 2012, at 10:40 AM, Jason Orendorff wrote:

> On Fri, Nov 30, 2012 at 7:40 PM, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:
> Overall, I like this approach. However, I don't think @@create belongs on the prototype object.  This make the @@create functionality for a particular kind of object available to anyone who gets their hands on  an instance object of the class.  This smells like a capability leak.
> I think you're right.
> Instead, I would make @@create a property of the actual constructor function and I would place the default @@create on Function.prototype, which all functions inherit from.
> So roughly speaking, Foo.[[Constructor]](...args) would be defined as:
> 1) Let creator be Foo.[[Get]](@@create)
> 2 ) Let newObj be creator.call(foo).  //Foo is passed as the this value to @@create
> 3)  Let ctorResult be Foo.[[call]](newObj,args)
> 4)  If Type(ctorResult) is Object, return ctorResult
> 5) else return newObj
> The definition of the Function.prototype.@@create would be loosely
> function() {
>     return Object.create(this.prototype);
> }
> So, Map would be defined with a Map.@@create method that in sorta specTalk would say something like:
> 1) Let obj be a new ordinary object.
> 2) Let proto be this.[[Get]]('Prototype').
> 2) Set the [[Prototype]] of obj to proto.
> 3)  Add a [[MapData]] internal property to obj.
> 4) Return obj
> All of this looks pretty great to me, certainly an improvement on what I proposed. This is actually similar to what Python does. I wonder if it can be simplified a bit.

It's also essentially what Smalltalk (basicNew) and Ruby (allocate) do, so we're probably on the right track.

> One issue with this is initializing fields that we want to be immutable. Consider subclassing something like the String wrapper class: String.@@create() should return an object with [[PrimitiveValue]] set to... an empty string? It gets initialized later? It's kind of gross for that field to change observably after allocation. Python fixes this by adding another wrinkle: it passes the constructor arguments to the allocation hook (__new__) as well as the initialization hook (__init__). Maybe we can achieve the same end by simplifying instead.

Do you have a simplification in mind?

I'm reluctant to pass the new arguments to both.  Argument passing has a runtime cost and new occurs a lot.  Plus, as soon as you pass the same arguments to both you muddy the waters about the purpose of the @@create method and the constructor function.  Without the arguments its a clean separation.  One allocates  the object and slots for its private state.  The other initializes the object, including both private and public state.  If we pass the new arguments to @@create people some people are going to get mixed up about this.

As you move into complex objects with complex initialization semantics it becomes pretty much impossible to guarantee that a half initialized object hasn't escaped from a constructor.  All it takes is a global assigned of the constructor this value while the constructor is active followed by a throw out of the constructor. That sort of thing is a (relatively rare, I think) bug that people just have to deal with.

I'm not sure that leaking a not fully initialized string wrapper object would be any worse that the half initialized user defined object.  A perhaps hard to find but rare bug.  My inclination is to live with the bug possibility and keep the simplicity of a no-args @@reate call.

BTW,  since the spec.  will simply define the [[PrimitiveValue]] state (it's now named something else in the spec...) as an "internal data property"  which basically just means "private slot", an implementation could do magic to ensure such an object never exposed the [[PrimitiveValue]] before it is initialized. For example, it could implement it as a special single-assignent slot that that throws if read before it is initialized. Or it just might initialize it with a special marker value that all of the built-in methods that know how to access the slot check for.  (If it really is implemented using a private named property, then the private name itself is a pretty good thing to use as the uninitialized value marker.)

The simplification I've thought about is eliminating [[Construct]] as an internal method/Proxy trap and just making the call @@Create/call consturctor sequence the evaluation semantics of the new operator.  But I've not yet convinced myself that this is sufficient to capture all of the "called as constructor"/"called as a function" semantic silliness that some chapter 15 built-ins have. I'm also not sure that DOM and friends don't have other dependencies on a reified [[Construt]]


> -j

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20121201/2bc3552f/attachment.html>

More information about the es-discuss mailing list