DJG
DJG

Reputation: 6563

Explain this part of Arrowlets source code

I was looking through the Arrowlets source code, and found this section near the top:

/*
 * Box: a temporary (singleton) place to put stuff. Used as a helper for constructors with variadic arguments.
 */
function Box(content) {
    Box.single.content = content;
    return Box.single;
}
/* JavaScript hackery based on the strange semantics of "new":
 * - Box() assigns Box.single.value, so Box.single has to be defined;
 * - properties can be assigned to numbers (but have no effect);
 * - when Box.single = 1 (or any non-Object), "new Box" returns "this". */
Box.single = 1;
Box.single = new Box;
Box.prototype.toString = function Box$prototype$toString() {
    return "[Box " + this.content + "]";
}

I also looked through some of Box's uses in the source code, and it seems like it is another way to pass multiple arguments, but I really don't understand how. Also, the comments state that:

when Box.single = 1 (or any non-Object), "new Box" returns "this".

But I thought whenever a constructor function was called with new, this would be returned. Can someone please explain this to me?

UPDATE:

I'm having a hard time understanding why Box.single has to be set to a non-object for this approach to work and what is gained with the trickery using the new operator. Examples from a NodeJS repl:

No new and using a non-object

> function Box(content) {
... Box.single.content = content;
... return Box.single;
... }
Box.single = {}; // NOTE: Setting Box.Single to an Object
{}
> //NOTE: Not using the "new" operator at all
undefined
> Box(23)
{ content: 23 }
> Box.single
{ content: 23 }
> Box({'name': 'John'})
{ content: { name: 'John' } }
> Box.single
{ content: { name: 'John' } }

Using new and an object

> function Box(content) {
... Box.single.content = content;
... return Box.single;
... }
undefined
> Box.single = {}; // Setting Box.single to an object
{}
> Box.single = new Box; // Using the new operator
{ content: undefined }
> Box({'name': 'John'})
{ content: { name: 'John' } }
> Box.single
{ content: { name: 'John' } }

as opposed to using the method from Arrowlets:

> function Box(content) {
... Box.single.content = content;
... return Box.single;
... }
undefined
> Box.single = 1; // Setting Box.single to a Non-Object
1
> Box.single = new Box; // Using the new operator
{}
> Box(23)
{ content: 23 }
> Box({'name': 'John'})
{ content: { name: 'John' } }
> Box.single
{ content: { name: 'John' } }

It seems like the arrowlets approach is just a convoluted way of accomplishing something simple. What am I missing?

Upvotes: 0

Views: 197

Answers (2)

khooyp
khooyp

Reputation: 21

The difference is in instanceof:

...
Box.single = {};
Box.single = new Box; // with or without this line
Box(1) instanceof Box; // false

and:

...
Box.single = 1;
Box.single = new Box;
Box(1) instanceof Box; // true

In the first case, because Box.single is an object, the call above to new Box returns Box.single which is a plain {} object, according to the JavaScript rules. Every subsequent call to new Box or Box returns that same plain object.

In the second case, Box.single is not an object, so new Box returns a new Box object and assigns it to Box.single, and every subsequent call to new Box or Box returns that same Box object.

Note that, perhaps surprisingly, new does not have to return this; it can return any obj where typeof(obj) == "object" by calling return obj (if typeof(obj) != "object", then new returns this). E.g.:

function Foo() {
    this instanceof Foo; // true
}
function Bar() {
    this instanceof Bar; // true
    return new Foo();
}
(new Bar) instanceof Bar; // false
(new Bar) instanceof Foo; // true

That's also why new Box can be used instead of just Box:

a = Box(1);
b = new Box(2);
a === b; // true

Upvotes: 2

Bill Enright
Bill Enright

Reputation: 48

The Arrowlets code never uses this function with "new", except for this one call:

Box.single = new Box;

So your comment/concern about "this" is relevant only to this one use. You can see that Box is a function, and that Box has a property called single. With the above assignment, Box.single is set to point to the instance of the Box function - the 'this' returned by new Box;

Before Box() is called as a constructor, some trickery is needed in order for the call to be successful. In that case, Box.single has been set to the value of 1, so that setting the content of single in:

Box.single.content = content;

will be harmless/ignored/however you want to think of it. "content" will be undefined with the

new Box;

call. We don't want the constructor to throw. Hence the trickery. Upon return from the constructor call, Box.single is set to the instantiated Box()

The developer is trying to create a singleton that holds on to "content". The goal is to work with Box() in order to package an arbitrary number of arguments together. This has to do with making many-parameter JavaScript functions work with Tuples which underlie the Arrowlets implementation. Here the Box is used to create a Tuple when there are more than 2 elements in an array:

Tuple.fromArray = function Tuple$fromArray(array) {
    switch (array.length) {
        case 0:
        case 1:
            return array[0];
        case 2:
            return new Pair(array[0], array[1]);
        default:
            return new Tuple(Box(array));
    }
}

When a Tuple is created with the constructor below, if a Box is passed in, the content of the box becomes the 'components' of the Tuple

function Tuple() {
    if (arguments[0] instanceof Box) {
        var components = arguments[0].content;
    } else {
        [...omitted stuff...]
    }
    /* properties */
    this.components = components;
    this.length = components.length;
}

That's all that's going on!

Upvotes: 1

Related Questions