A more efficient $super for Prototype 2

30 views
Skip to first unread message

T.J. Crowder

unread,
Sep 4, 2009, 8:15:43 AM9/4/09
to Prototype: Core
Hi all,

I've come up with a new way of handling $super (which I can't imagine
is unique, it's just new to me) which is markedly more efficient than
our current $super on all of the browsers I've tried it on. I'm
wondering whether we should consider it for Prototype 2.

Executive summary:

Pros -
* Avoids calling toString on subclass methods when defining classes.
* Avoids creating closures for each $super-enabled subclass method.
* Avoids creating an on-the-fly function on every call to a $super-
enabled method.
* Avoids at least four extra calls when $super-enabled methods are
called.
* Consequently reduces the time to call methods that use $super, from
markedly (1.6X as fast) to dramatically (10X as fast) depending on
browser and choices the subclass author makes.
* Theoretically reduces memory footprint (no added functions/
closures).
* Minifiers (like packer3) that change arg names don't break things by
renaming $super.
* (Subjective) Simpler code in Class.create for contribs to
understand.

Cons -
* Breaks backward compatibility.
* Syntax for calling the $super method is slightly more complex.

Details:

A brief review of how $super currently works:

1. When a class is defined, Class.create calls Function#argumentNames
on each method defined by the subclass. (#argumentNames is
Function#toString followed by three regexes and a String#split.)
Class.create then checks the first argument to see if its name is
"$super" and, if so, creates a closure to hold the ancestor and wraps
the member function to set up the $super goodness.

2. When a $super-enabled method is called, it is in fact our wrapper
that is called. The wrapper creates a new function on the fly (on
every call) to bind our closure to "this" and passes the result to the
subclass's real method as the $super parameter.

This results in the wonderfully simple syntax supported by Prototype
classes, that you can call the super's version of a function simply by
calling $super:

nifty: function($super, foo, bar) {
$super(foo);
}

You don't even have to pass in "this", it's all handled for you,
though at a substantial runtime cost. (Note that in this example,
only 'foo' and not 'bar' is passed to the super; presumably 'bar' has
meaning only to the subclass.)

The new approach is markedly simpler and more direct at both the class
definition stage and the method call stage. However, calling the
$super version of the function is not quite as straightforward.

Here's how the new approach works:

1. At class definition time, Class.create detects that the subclass
overrides the superclass's method (this is a trivial property check)
and, if so, stores a reference to the super's function as a property
called $super on the sub's function object.

2. When a method is called, there is no indirection, the call goes
direct to the subclass's defined method. The subclass can then call
the super's version via arguments.callee.$super.

The unaided syntax subclass methods would use to call the super's
version is complicated and error-prone (to the novice, anyway):

nifty: function(foo, bar) {
arguments.callee.$super.call(this, foo);
}

...and so this solution envisions a helper "callSuper" method (name
TBD) authors may choose to use instead (at the cost of an extra
function call):

nifty: function(foo, bar) {
this.callSuper(arguments, foo);
}

You can see why I think a helper may be useful; that's quite a lot
simpler. Note that the first argument is always 'arguments' and does
*not* mean #callSuper is passing all of the arguments to the super
function; you follow it with the arguments you want to pass. We can
muck about with the API on the helper, and of course class authors can
create their own for their own needs.

In my tests on Windows, IE shows the least improvement and Chrome the
most. Specifically, my simple tests so far (all of these numbers are
rounded, and they vary quite a lot from test to test, so these are
just indicative):

Using callSuper syntax:
* IE: 38% reduction in method call time, e.g., calls are 1.6 times as
fast
* Safari: 58% reduction; 2.4 times as fast
* Firefox: 65% reduction; 2.9 times as fast
* Opera: 66% reduction; 2.9 times as fast
* Chrome: 83% reduction, calls are 5.8 times as fast

Using direct (but complicated) syntax:
* IE: 40% reduction, 1.7 times as fast
* Safari: 77% reduction; 4.4 times as fast
* Opera: 77% reduction; 4.4 times as fast
* Firefox: 80% reduction; 4.9 times as fast
* Chrome: 91% reduction, calls are 10.1 times as fast

Using the complicated syntax, that's a full order of magnitude *or
better* on Chrome (but let's face it, Chrome is already so much faster
than everything else it's less important than it would otherwise be),
and Firefox sees ~5X improvement. With the syntax I would expect most
class authors to use, the benefit is less but still marked.

Call overhead isn't sexy, but does anyone else think this is worth
looking at more closely?
--
T.J. Crowder
tj / crowder software / com
www.crowdersoftware.com

Robert Kieffer

unread,
Sep 4, 2009, 8:53:36 AM9/4/09
to prototy...@googlegroups.com
Hey gang,

I took a look at this issue a while ago, in a blog post that generated a fair bit of discussion and spin off investigations (see http://www.google.com/search?q=inheritance+performance+javascript) so I'll try to weigh in.  However I'm in the middle of an extended road trip, so I don't have the time/resources necessary to give this proper attention...

http://ejohn.org/blog/simple-javascript-inheritance - JQ's swipe at this problem.  Definitely read through the comments.  Your approach appears similar to his (which is based on Dean Edward's Base2).  Dean uses this.base(), instead of this.callSuper(), fwiw.  no strong opinions there, although this.base() seems a bit more "prototype-like" in it's terseness.

The performance tests I did for Base2 (see the broofa.com article in the google search above) are probably a good first-guess at the performance your approach will have compared to other techniques.

Technically this doesn't break backward compatibility - $super and this.callSuper() can coexist as dual techniques for doing inheritance, correct?  We just need to decide if $super support will continue in P2.

... and, finally, I vaguely recall people raising some interesting edge cases.  But I don't remember where I saw those.  *sigh*.

- rwk

T.J. Crowder

unread,
Sep 4, 2009, 9:09:32 AM9/4/09
to Prototype: Core
Hi Richard,

Thanks for that.

I like the terseness and clarity of this.base() although I'd have
concerns about that making it difficult for people to retrofit their
classes ("base" being a fairly common word). I do _not_ mean that I
think "callSuper" is a great name; I don't and I expect suggestions
like yours to result in a better name. But something unlikely to
clash makes it simpler for people to do a global search-and-replace to
convert $super(...) to this.niftyNameHere(arguments, ...). Even in a
large project, that should be a 10-minute exercise.

Agreed that both mechanisms can co-exist, except that I -- perhaps
unreasonably -- find running all of the subclass methods through
Function#toString at definition time ugly. But you're right, once
deprecated the old $super could be supported for a dot rev or so to
ease transition.

I meant to mention in my earlier post that once implementations have a
native Function#bind (which I'm sure everyone knows is in the latest
ECMA draft specs), presumably the cost of that bind-on-each-method-
call would go down, though I suspect it will never reduce to zero.
--
T.J. Crowder
tj / crowder software / com
www.crowdersoftware.com


On Sep 4, 1:53 pm, Robert Kieffer <bro...@gmail.com> wrote:
> Hey gang,
>
> I took a look at this issue a while ago, in a blog post that generated a
> fair bit of discussion and spin off investigations (seehttp://www.google.com/search?q=inheritance+performance+javascript) so I'll
> try to weigh in.  However I'm in the middle of an extended road trip, so I
> don't have the time/resources necessary to give this proper attention...
>
> http://ejohn.org/blog/simple-javascript-inheritance- JQ's swipe at this

Allen Madsen

unread,
Sep 4, 2009, 10:00:45 AM9/4/09
to prototy...@googlegroups.com
Hi,

I think the speed improvement definitely merits going into. I would suggest the helper be called $super so when converting, the relationship is evident. I have to admit that passing arguments in feels odd to me though. 

Jim Higson

unread,
Sep 4, 2009, 10:05:45 AM9/4/09
to prototy...@googlegroups.com, T.J. Crowder

On Friday 04 September 2009 14:09:32 T.J. Crowder wrote:
> [...] I do _not_ mean that I

> think "callSuper" is a great name; I don't and I expect suggestions
> like yours to result in a better name. But something unlikely to
> clash makes it simpler for people to do a global search-and-replace to
> convert $super(...) to this.niftyNameHere(arguments, ...).

How about this.$super()?

--
Jim
my wiki ajaxification thing: http://wikizzle.org
my blog: http://jimhigson.blogspot.com/

T.J. Crowder

unread,
Sep 4, 2009, 10:09:49 AM9/4/09
to Prototype: Core
@Allen & @Jim,

Gets my vote.

-- T.J. :-)

T.J. Crowder

unread,
Sep 4, 2009, 11:27:34 AM9/4/09
to Prototype: Core
Hi again folks,

This thread[1] about using Array.prototype.slice to copy arguments
made me wonder whether the speed improvements I found had more to do
with the fact my implementation of the new mechanism had its own copy-
the-args function (which is faster than using `slice` on most
browsers; see the thread for details) than with eliminating calls and
function creation. And the answer is: No, but with the important
caveat: On Chrome when using the "callSuper" helper, a big part of
the gains were indeed down to the copy-the-args function instead of
`slice`. If I modify my new mechanism to use `slice` instead (which
obviously is the wrong way 'round, but never mind), when using the
helper the new mechanism is only about 2X faster on Chrome than the
old mechanism. Of course, Firefox got faster in that scenario (since
Firefox is the one browser in my set of tests where using `slice` is
faster than one's own copy function).

So summing up, the new mechanism remains faster across-the-board even
using the helper, but the gains on Chrome will not be as marked (when
using a helper) as they seem at present because of the difference in
how args are copied. And of course, one of the great things about the
new mechanism is that you don't have to copy args at all if you are
willing to use the more verbose syntax.

[1] http://groups.google.com/group/prototype-core/browse_thread/thread/cf8c287e231a0192#
--
T.J. Crowder
tj / crowder software / com
www.crowdersoftware.com

kangax

unread,
Sep 4, 2009, 1:25:19 PM9/4/09
to Prototype: Core
On Sep 4, 8:15 am, "T.J. Crowder" <t...@crowdersoftware.com> wrote:
> Hi all,
>
> I've come up with a new way of handling $super (which I can't imagine
> is unique, it's just new to me) which is markedly more efficient than
> our current $super on all of the browsers I've tried it on. I'm
> wondering whether we should consider it for Prototype 2.

Our current solution is clearly one of the slowest inheritance
implementations out there. You get very nice sugar for a very high
price :) No surprises there. I think P2 should definitely employ a
simpler approach (either fully replacing old one or just as a separate
option).

It's worth mentioning that one of the warts of current solution is
that it relies on function decompilation - something that's known to
cause problems [1][2] and is better avoided (fwiw, ES committee didn't
have enough time to handle this "issue" so ES5 specifies
`Function.prototype.toString` to return the very same *implementation-
dependent* function representation)

>
> Executive summary:
>
> Pros -
> * Avoids calling toString on subclass methods when defining classes.
> * Avoids creating closures for each $super-enabled subclass method.
> * Avoids creating an on-the-fly function on every call to a $super-
> enabled method.
> * Avoids at least four extra calls when $super-enabled methods are
> called.
> * Consequently reduces the time to call methods that use $super, from
> markedly (1.6X as fast) to dramatically (10X as fast) depending on
> browser and choices the subclass author makes.
> * Theoretically reduces memory footprint (no added functions/
> closures).
> * Minifiers (like packer3) that change arg names don't break things by
> renaming $super.
> * (Subjective) Simpler code in Class.create for contribs to
> understand.
>
> Cons -
> * Breaks backward compatibility.
> * Syntax for calling the $super method is slightly more complex.

* Relies on `arguments.callee` with all the consequences.

To explain consequences:

`arguments.callee` is considered one of the warts of ES3. Besides
certain security concerns, the bad part of it is that it requires
creation of `arguments` object (when entering execution context). This
creation is rather expensive and results in both - memory and perf.
hits. Some of the modern implementations optimize in such way that
they only create `arguments` object when it statically occurs in a
function body (or when there's a chance of dynamic evaluation, such as
`eval`, `setTimeout` occurrence, etc.). Others create `arguments`
object on first resolution of `arguments` identifier, etc.

You can easily see it by yourself. Create 2 identical functions, one
of which would use `arguments.callee` and another - plain identifier;
then look at a difference.

ES5 also introduces strict mode. When in strict mode,
`argument.callee` throws TypeError on access. IIRC, Caja (secure
subset of ES) and some of its variations (Cajita, Valija), emulate ES5-
strict behavior and also throw error. If something in Prototype were
to rely on `arguments.callee`, it would effectively prevent running it
in strict mode or cajoling it.

[snip current approach explanation]

> Here's how the new approach works:
>
> 1. At class definition time, Class.create detects that the subclass
> overrides the superclass's method (this is a trivial property check)
> and, if so, stores a reference to the super's function as a property
> called $super on the sub's function object.
>
> 2. When a method is called, there is no indirection, the call goes
> direct to the subclass's defined method. The subclass can then call
> the super's version via arguments.callee.$super.
>
> The unaided syntax subclass methods would use to call the super's
> version is complicated and error-prone (to the novice, anyway):
>
> nifty: function(foo, bar) {
> arguments.callee.$super.call(this, foo);
> }
>
> ...and so this solution envisions a helper "callSuper" method (name
> TBD) authors may choose to use instead (at the cost of an extra
> function call):
>
> nifty: function(foo, bar) {
> this.callSuper(arguments, foo);
> }
>
> You can see why I think a helper may be useful; that's quite a lot
> simpler. Note that the first argument is always 'arguments' and does
> *not* mean #callSuper is passing all of the arguments to the super
> function; you follow it with the arguments you want to pass. We can
> muck about with the API on the helper, and of course class authors can
> create their own for their own needs.

I usually use a similar approach instead of `$super`, which I first
saw mentioned by Tobie some time ago:

Class.Methods.callSuper = function(methodName) {
var fn = this.constructor.superclass.prototype[methodName];
return (arguments.length > 1)
? fn.apply(this, _slice.call(arguments, 1))
: fn.call(this);
}

then:

initialize: function(options) {
...
this.callSuper('initialize', options);
...
}

One huge downside to this is that there's now repetition of method
name. If I change method name, I also have to remember to change first
argument of `callSuper` somewhere in the function body. The good thing
is that this approach is very efficient. There's also occurrence of
`arguments` there, but not `arguments.callee`.

[snip tests]

> Using the complicated syntax, that's a full order of magnitude *or
> better* on Chrome (but let's face it, Chrome is already so much faster
> than everything else it's less important than it would otherwise be),
> and Firefox sees ~5X improvement. With the syntax I would expect most
> class authors to use, the benefit is less but still marked.
>
> Call overhead isn't sexy, but does anyone else think this is worth
> looking at more closely?

Definitely :)

[...]

[1] http://thinkweb2.com/projects/prototype/detecting-built-in-host-methods/
[2] http://thinkweb2.com/projects/prototype/those-tricky-functions/

T.J. Crowder

unread,
Sep 4, 2009, 2:54:42 PM9/4/09
to Prototype: Core
Hi Juriy,

Thanks for that. Yes, it was the function decompilation (and the on-
the-fly bind) that made me look for another solution.

I was unaware of the performance implication of arguments.callee, and
*WOW* does it make a speed difference (see below). The good thing is
that this solution doesn't require arguments.callee (we'll have to
adjust the helper, or do away with it). Just name your functions and
use the name. You _can_ use arguments.callee if you like, but for
best results, use function names.

Because **HOLY COW** what a difference that makes. Using the
function's name instead makes the new mechanisn 16-50X faster (I kid
you not) than the old on Chrome. Firefox also goes through the roof
speed-wise, we're talking 35X+. Even IE is more impressed, going from
1.6X to 2.4X as fast. Opera says "yes, please" with a 11X
improvement, as does Safari with 10X.

The fly in the ointment is that IE will not acknowledge the function
name if defined as part of an assignment/initialization statement like
so:

var Thingy = Class.create({
nifty: function nifty(foo, bar) {
nifty.$super.call(this, foo);
}
});

It just ignores the function name and complains that 'nifty' is not
defined. This works:

var Thingy = Class.create((function() {
function nifty(foo, bar) {
nifty.$super.call(this, foo);
}

return {nifty: nifty};
})());

(Which is what a lot of the Prototype source now does.) So that's a
larger impact on how you define classes, but also suggests a useful
pattern to authors for sharing common private class methods/data
within the closure, and WOW what a performance difference.

There seems to be enough interest in this that it's worth my doing a
sample changeset with test cases and sharing it 'round; I'll do that
over the weekend.
--
T.J. Crowder
tj / crowder software / com
www.crowdersoftware.com


kangax

unread,
Sep 5, 2009, 12:16:30 AM9/5/09
to Prototype: Core


On Sep 4, 2:54 pm, "T.J. Crowder" <t...@crowdersoftware.com> wrote:
> Hi Juriy,
>
> Thanks for that.  Yes, it was the function decompilation (and the on-
> the-fly bind) that made me look for another solution.
>
> I was unaware of the performance implication of arguments.callee, and
> *WOW* does it make a speed difference (see below).  The good thing is
> that this solution doesn't require arguments.callee (we'll have to
> adjust the helper, or do away with it).  Just name your functions and
> use the name.  You _can_ use arguments.callee if you like, but for
> best results, use function names.

Sounds good.

>
> Because **HOLY COW** what a difference that makes.  Using the
> function's name instead makes the new mechanisn 16-50X faster (I kid
> you not) than the old on Chrome.  Firefox also goes through the roof
> speed-wise, we're talking 35X+.  Even IE is more impressed, going from
> 1.6X to 2.4X as fast.  Opera says "yes, please" with a 11X
> improvement, as does Safari with 10X.

Told ya :)

>
> The fly in the ointment is that IE will not acknowledge the function
> name if defined as part of an assignment/initialization statement like
> so:

That's a so-called "named function expression".

>
>     var Thingy = Class.create({
>         nifty: function nifty(foo, bar) {
>             nifty.$super.call(this, foo);
>         }
>     });
>
> It just ignores the function name and complains that 'nifty' is not
> defined.  This works:

This is strange. IE should parse `nifty` as a *function declaration*
in this case, and make available in the enclosing scope (against
specs). I don't see why `nifty` identifier would not be available in a
function. Take a look at my NFE article - <http://yura.thinkweb2.com/
named-function-expressions/> - for all the gory details about this
particular IE idiocy (and a little more on the same subject) :)

>
>     var Thingy = Class.create((function() {
>         function nifty(foo, bar) {
>             nifty.$super.call(this, foo);
>         }
>
>         return {nifty: nifty};
>     })());
>
> (Which is what a lot of the Prototype source now does.)  So that's a
> larger impact on how you define classes, but also suggests a useful
> pattern to authors for sharing common private class methods/data
> within the closure, and WOW what a performance difference.

Yep, giving function an identifier is quite helpful in debugging/
profiling, or even plain introspection in console.

>
> There seems to be enough interest in this that it's worth my doing a
> sample changeset with test cases and sharing it 'round; I'll do that
> over the weekend.

Great! Thanks.

[...]

--
kangax

Allen Madsen

unread,
Sep 5, 2009, 9:46:16 AM9/5/09
to prototy...@googlegroups.com
>
>     var Thingy = Class.create({
>         nifty: function nifty(foo, bar) {
>             nifty.$super.call(this, foo);
>         }
>     });
>
> It just ignores the function name and complains that 'nifty' is not
> defined.  This works:

I think you need "this.nifty.$super.call(this, foo);"

Allen Madsen
http://www.allenmadsen.com

T.J. Crowder

unread,
Sep 6, 2009, 3:48:09 AM9/6/09
to Prototype: Core
Hi Allen,

> I think you need "this.nifty.$super.call(this, foo);"

You don't, although that also works. A named function's name is in
scope within the function:

function foo(bar) {
alert(typeof foo); // Alerts "function"
}

However, I was thinking about anonymous functions this morning while
waking up (pathetic, isn't it?) and realized that even if you don't
use a named function, you can avoid arguments.callee with exactly the
form you describe: this.nifty.$super.call(this, foo); I don't like
the repetition of "this", but if you don't have time to switch
everything over to named functions (I'm thinking of retrofitting
efforts), it's a reasonable first step, and I assume (haven't tested
it yet) still gets speed gains over arguments.callee.

Will be posting a sample implementation (it's wonderfully simple, but
there are a couple of edge cases around dynamic updates of classes)
and tests ASAP, but I want to get some of the Prototype documentation
issues sorted out first (transferring the old doc to the new format
and updating), since that's more urgent.

-- T.J. :-)

T.J. Crowder

unread,
Sep 6, 2009, 5:38:36 AM9/6/09
to Prototype: Core
> > I think you need "this.nifty.$super.call(this, foo);"
>
> You don't, although that also works.

Ack! Sorry, too early in the morning, hadn't had enough coffee.

That does *not* work, because you always refer to the bottommost
subclass's nifty ("this.nifty" is always the bottommost function), so
you end up endlessly recursing. Wow that's an easy trap to fall into.

No, it's either use the function's name unadorned, or use
arguments.callee and pay the performance penalty. Personally, I
prefer using the function's name. :-)

-- T.J. :-)

Rick Waldron

unread,
Sep 7, 2009, 2:53:08 PM9/7/09
to prototy...@googlegroups.com
TJ...

I once woke up from a nap in the middle of a saturday and solved a silly issue i was having with event bubbling/propagation... I had seen the solution in code as the last thing before I woke up. I tried it... and it worked. The point: never feel bad about waking up to code on the brain :)

Rick

Allen Madsen

unread,
Sep 7, 2009, 10:57:13 PM9/7/09
to prototy...@googlegroups.com
TJ,

I guess I don't understand why it wouldn't work. I'll illustrate how I understand it and you correct me where I'm wrong.

var A = Class.create({
  nifty: function(){}
});

var B = Class.create(A, {
  nifty: function(){
    this.nifty.$super(); // refers to A.nifty()
    //this.nifty(); //would cause recursion
  }
});

Also, if what I'm thinking is correct, there are some interesting side effects that weren't possible before. Such as:

var C = Class.create(B, {
  nifty: function(){
    this.nifty.$super.$super(); //refers to A.nifty() instead of B.nifty()
  },
  crappy: function(){
    this.nifty.$super(); //calls super of another function
  }
});
On Sun, Sep 6, 2009 at 5:38 AM, T.J. Crowder <t...@crowdersoftware.com> wrote:

T.J. Crowder

unread,
Sep 8, 2009, 12:24:19 AM9/8/09
to Prototype: Core
Hi Allen,

> I guess I don't understand why it wouldn't work.

Yeah, it's...subtle. :-) It runs into what I call the "grandchild"
problem. Consider Parent < Child < GrandChild, all with #nifty
functions:

var Parent = Class.create((function() {

function nifty(spiffy)
{
return "P(" + spiffy + ")";
}

return {
nifty: nifty
};
})());
var Child = Class.create(Parent, (function() {

function nifty(spiffy)
{
return nifty.$super.call(this, spiffy) + " < C";
}

return {
nifty: nifty
};
})());
var GrandChild = Class.create(Child, (function() {

function nifty(spiffy)
{
return nifty.$super.call(this, spiffy) + " < GC";
}

return {
nifty: nifty
};
})());

We instantiate a GrandChild instance and call GrandChild#nifty:

var gc = new GrandChild();
gc.nifty('cool');

Inside our methods, "this" always refers to the actual instance, and
so "this.nifty" always refers to the "nifty" function referenced by
the instance, which is always GrandChild#nifty. So within
GrandChild#nifty, this.nifty.$super will reference Child#nifty and
using that form will work -- but only one level deep. Because then
within Child#nifty, if we use this.nifty.$super, nothing is diffrent
-- "this" still refers to the instance, and so "this.nifty" still
refers to GrandChild#nifty, and so "this.nifty.$super" still refers to
Child#nifty. If Child#nifty calls this.nifty.$super, it will call
itself and recurse.

Another way to say that is that within the various #nifty functions,
"this.nifty !== nifty" except at the bottom (GrandChild) level.

The reason that the syntax without "this" works is that the name of a
function is in scope within the function, and it refers to *that
specific function*. So an unadorned "nifty" within GrandChild#nifty
refers to GrandChild#nifty, within Child#nifty refers to Child#nifty,
and within Parent#nifty refers to Parent#nifty. So "nifty.
$super" (without "this") always gives us our super function.

I'm posting an implementation (it was really easy to mod the source to
support this, including handling dynamic changes to the hierarchy) and
unit tests in a little bit.

-- T.J. :-)

On Sep 8, 3:57 am, Allen Madsen <bla...@gmail.com> wrote:
> TJ,
> I guess I don't understand why it wouldn't work. I'll illustrate how I
> understand it and you correct me where I'm wrong.
>
> var A = Class.create({
>   nifty: function(){}
>
> });
>
> var B = Class.create(A, {
>   nifty: function(){
>     this.nifty.$super(); // refers to A.nifty()
>     //this.nifty(); //would cause recursion
>   }
>
> });
>
> Also, if what I'm thinking is correct, there are some interesting side
> effects that weren't possible before. Such as:
>
> var C = Class.create(B, {
>   nifty: function(){
>     this.nifty.$super.$super(); //refers to A.nifty() instead of B.nifty()
>   },
>   crappy: function(){
>     this.nifty.$super(); //calls super of another function
>   }
>
> });
>
> Allen Madsenhttp://www.allenmadsen.com

Jim Higson

unread,
Sep 8, 2009, 3:52:58 AM9/8/09
to prototy...@googlegroups.com
On Tuesday 08 September 2009 03:57:13 Allen Madsen wrote:
> TJ,
> I guess I don't understand why it wouldn't work. I'll illustrate how I
> understand it and you correct me where I'm wrong.
>
> var A = Class.create({
> nifty: function(){}
> });
>
> var B = Class.create(A, {
> nifty: function(){
> this.nifty.$super(); // refers to A.nifty()
> //this.nifty(); //would cause recursion
> }
> });

Wouldn't you have to do this to preserve scope?

this.nifty.$super.call( this );

Myself, I've never liked the .call syntax much because I mistake it with
apply: the two words seem arbitrary. But, yeah, I can see that pre-binding
every overridden method of every object would be a big overhead.

Perhaps I would do:

var B = Class.create(A, {
nifty: function(){

var $super = this.nifty.$super.bind( this );
// $super refers to A.nifty() bound to this
$super();
}
});

[1] Please correct if this is wrong!

T.J. Crowder

unread,
Sep 8, 2009, 4:28:00 AM9/8/09
to Prototype: Core
Hi Jim,

> Perhaps I would do:
>
> var B = Class.create(A, {
> nifty: function(){
>
> var $super = this.nifty.$super.bind( this );
> // $super refers to A.nifty() bound to this
> $super();
> }
>
> });
>
> [1] Please correct if this is wrong!

You need to leave out the "this." and use a named function instead;
see my note to Allen for the details. You can use #bind if you want
to, but in my experience you're typically only making a single call to
the superclass method, so it's not really worth the overhead. But
here's what it would look like:

var B = Class.create(A, (function(){

function nifty(){
var $super = nifty.$super.bind( this );
// $super refers to A.nifty() bound to this
$super();
}

return {nifty: nifty};
})());

-- T.J.

T.J. Crowder

unread,
Sep 8, 2009, 5:55:30 AM9/8/09
to Prototype: Core
Hi all,

I've taken a break from porting documentation from Mephisto into the
source (zzzzzz) to do a first take on implementing the new
supercalls! Here's the commit in a branch in my repo:[1]

That commit includes the code, documentation, and unit tests. The old
class unit tests are still there (I've just renamed them "oldclass",
though not properly from a git history POV I just realized) so we
continue to test the old style until we remove it, which I would
assume would be a couple of versions after introducing the new one.
I've also added tests to prove that you can mix the two styles (derive
from an old-style using the new style, or vice-versa), not that I
recommend doing that. Apologies in advance if I'm out on a limb
stylistically; I tried to fit in (inline vars! yuck!). If I've gone
astray, please let me know.

As you can see, the implementation in the Prototype code is dead
simple, and yet the new supercalls are easy to use, particularly if
you're already sold on the benefits of using named rather than
anonymous functions (or if you don't care how phenomenally slow
`arguments.callee` is).

This gist[2] contains a basic performance comparison page. The change
really is dramatic, even if you use arguments.callee -- but it's mind-
blowing if you use named functions (thanks, kangax, for pointing that
out!).

About dynamism: Coming from the compiled language world, I would
never have thought to handle people adding methods to classes on-the-
fly AFTER the class may have been derived; thank you to Tobie for
pointing out that requirement. The code correctly handles defining
new methods and redefining methods, ensuring that everybody is linked
up correctly. (That turned out to be trivial.) But I have a couple
of questions for you dynamic types:

1) Does it need to support _removing_ a method entirely? (Currently
does not; easy to add.)

2) Does it need to handle edge cases where a name is defined on
classes but is not a function (e.g., a class variable), but then we
REdefine it to be a function? Or vice-versa (replacing a function with
a non-function)? (Currently does not handle it; will complicate the
code somewhat.)

I can just about imagine (1), but frankly if you're doing (2) I think
you need to revisit what you're doing! :-)

[1] http://github.com/tjcrowder/prototype/commit/79d3e1dfd32220299f0a5aceacfc6fd3ffa2a089
[2] http://gist.github.com/182817

Comments/suggestions/jeers welcome. (Well, no, not jeers.)

-- T.J.

Radoslav Stankov

unread,
Sep 8, 2009, 6:20:17 AM9/8/09
to Prototype: Core
It really looks nicer :)

I was generally using this.constructor ... and so on. But this is
better. I haven't run it but I guest it is possible to do

this.method.$super.call(this, ...)
arguments.callee.$supper.call(this, ...)

Witch is nice.

Richard Quadling

unread,
Sep 8, 2009, 6:22:20 AM9/8/09
to prototy...@googlegroups.com
2009/9/8 T.J. Crowder <t...@crowdersoftware.com>:
I'm getting an error in Chrome when I click "Test functionality" ...

Test loops: (suggest about 5,000 for IE, 200,000 for Chrome,
100,000 for Firefox or Safari)
GOOD: OP(hi)
GOOD: OP(hi) < OC
GOOD: OP(hi) < OC < OGC
GOOD: NP(hi)
Exception: TypeError: Cannot call method 'call' of undefined

In FireFox ...

Test loops: (suggest about 5,000 for IE, 200,000 for Chrome, 100,000
for Firefox or Safari)
GOOD: OP(hi)
GOOD: OP(hi) < OC
GOOD: OP(hi) < OC < OGC
GOOD: NP(hi)
Exception: TypeError: nifty.$super is undefined

and in IE ...

Test loops: (suggest about 5,000 for IE, 200,000 for Chrome,
100,000 for Firefox or Safari)
--------------------------------------------------------------------------------
GOOD: OP(hi)
GOOD: OP(hi) < OC
GOOD: OP(hi) < OC < OGC
GOOD: NP(hi)
Exception: [object Error]


I ran ...

git clone git://github.com/tjcrowder/prototype.git crowder

to create a new local copy before doing a rake dist (did the
submodules bit too).

Seems I've got the standard version and not TJ's.



--
-----
Richard Quadling
"Standing on the shoulders of some very clever giants!"
EE : http://www.experts-exchange.com/M_248814.html
Zend Certified Engineer : http://zend.com/zce.php?c=ZEND002498&r=213474731
ZOPA : http://uk.zopa.com/member/RQuadling

T.J. Crowder

unread,
Sep 8, 2009, 6:26:20 AM9/8/09
to Prototype: Core
Hi,

> It really looks nicer :)

Thanks!

> I haven't run it but I guest it is possible to do
>
> this.method.$super.call(this, ...)

No, you need to drop the "this." from the beginning of that. See my
reply to Allen a couple up (or the PDoc comments here[1]) for why.

> arguments.callee.$supper.call(this, ...)

Yup, that works (assuming $super rather than $supper), although it's a
lot slower than using the function name. It's still markedly faster
than the current mechanism.

-- T.J. :-)

Richard Quadling

unread,
Sep 8, 2009, 6:32:55 AM9/8/09
to prototy...@googlegroups.com
2009/9/8 T.J. Crowder <t...@crowdersoftware.com>:
In the end I downloaded the archive and used src to rebuild a new prototype.

Chrome...
test loops: 500000
Old mechanism time: 9666ms
New mechanism time: 639ms
*** The test was a bit short, the timing data may be suspect; try more loops.
Improvement: 93.39%, e.g., old takes 15.13 times as long as new.


A bit short!!!! Half a million loops and it's a bit short!!!!

T.J. Crowder

unread,
Sep 8, 2009, 6:38:06 AM9/8/09
to Prototype: Core
Hi Richard,

You want to use the "supercalls" branch; I suspect you used master.
This should do it:

git clone git://github.com/tjcrowder/prototype.git
git checkout supercalls
rake dist

In any case, I see you got there in the end.

> A bit short!!!! Half a million loops and it's a bit short!!!!

Heh heh heh. Yeah, well, we wouldn't want to jump to conclusions
based on insufficient test data. :-) I have it print that out if the
total test time was less than 15 seconds.

Only a 15X improvement on Chrome, eh? I've seen it even higher than
that...

-- T.J. :-)

Radoslav Stankov

unread,
Sep 8, 2009, 8:03:44 AM9/8/09
to Prototype: Core
"git pull origin supercalls" is needed for getting not only the master
branch. So we have:

git clone git://github.com/tjcrowder/prototype.git
cd prototype
git branch supercalls
git checkout supercalls
git pull origin supercalls
rake dist

There must be a smarter way with this, but I'm still a git newbie.

Bw, this worked fine, since $super is attached to function

<pre>
var SuperKlass = Class.create({
update: function(){ console.log('super'); }
});

// written also as
var SuperKlass = Class.create((function(){
function update(){ console.log('super'); }
return {update: update};
})());


var SubKlass = Class.create(SuperKlass, {
update: function(){
this.update.$super.call(this);
console.log('sub');
}
});

(new SubKlass()).update()
</pre>

p.s. on my MacOX Leopard, it behaves very good

Safari4:
test loops: 100000
Old mechanism time: 686ms
New mechanism time: 123ms

Firefox 3.5
test loops: 100000
Old mechanism time: 14868ms
New mechanism time: 442ms

T.J. Crowder

unread,
Sep 8, 2009, 8:15:03 AM9/8/09
to Prototype: Core
Hi Radoslav,

> There must be a smarter way with this, but I'm still a git newbie.

Hey, you're better at it than I am. :-)

> Bw, this worked fine, since $super is attached to function

It only worked because you only had a Parent < Child hierarchy. As
soon as you throw a third level into it (the "grandchild test"), e.g.
Parent < Child < GrandChild, if any of the middle layers (Child, in
this case) tries to do a supercall, it will *endlessly recurse*.
Really. Again, please read my reply to Allen above[1] for details.

[1] http://groups.google.com/group/prototype-core/browse_thread/thread/db9ccdaae4f7f705/c84a73f979fafc2c?#c84a73f979fafc2c

-- T.J.

T.J. Crowder

unread,
Sep 8, 2009, 8:34:13 AM9/8/09
to Prototype: Core
Hi all,

Just validated Radoslav's steps, and they work (provided you add the
submodules stuff), although it takes a while because you have to
retrieve everything (including submodules). Instead, it's really easy
and very fast to just add my repo as a remote within your existing
local prototype repo, and the pull the supercalls stuff into a branch:

1. Get into your local prototype repo

2. Add mine as a remote source:

git remote add tjcrowder git://github.com/tjcrowder/prototype.git

3. Create and switch to a local "supercalls" branch

git branch supercalls
git checkout supercalls

4. Pull my stuff into it:

git pull tjcrowder supercalls

5. Build

rake dist

6. Profit!

Alternately, here's[1] a pre-baked copy for anyone who just wants to
grab it and play around, but of course it will go stale fairly
quickly.

[1] http://gist.github.com/182838

-- T.J.

On Sep 8, 1:15 pm, "T.J. Crowder" <t...@crowdersoftware.com> wrote:
> Hi Radoslav,
>
> > There must be a smarter way with this, but I'm still a git newbie.
>
> Hey, you're better at it than I am. :-)
>
> > Bw, this worked fine, since $super is attached to function
>
> It only worked because you only had a Parent < Child hierarchy. As
> soon as you throw a third level into it (the "grandchild test"), e.g.
> Parent < Child < GrandChild, if any of the middle layers (Child, in
> this case) tries to do a supercall, it will *endlessly recurse*.
> Really.  Again, please read my reply to Allen above[1] for details.
>
> [1]http://groups.google.com/group/prototype-core/browse_thread/thread/db...

Allen Madsen

unread,
Sep 8, 2009, 9:56:06 AM9/8/09
to prototy...@googlegroups.com
Hey TJ,

For your two suggested additions. I don't think I have ever had a use for either. One thing I would like to suggest though is that Class.create take an object or a function as an argument. Since it is essentially a requirement now to use a function to create named functions that can call super (disregarding callee), it would be nice if defining that was simplified. So something like:

var A = Class.create({
  nifty: function(){}
});

var B = Class.create(A, function(){
  function nifty(){
    nifty.$super.call(this);
  }

  return {nifty: nifty};
});

Allen Madsen

unread,
Sep 8, 2009, 10:02:29 AM9/8/09
to prototy...@googlegroups.com
Oh, also, you may want to throw something into update helper about your new method of getting super.

Jim Higson

unread,
Sep 8, 2009, 10:12:41 AM9/8/09
to prototy...@googlegroups.com
On Tuesday 08 September 2009 14:56:06 Allen Madsen wrote:
> Hey TJ,
> For your two suggested additions. I don't think I have ever had a use for
> either. One thing I would like to suggest though is that Class.create take
> an object or a function as an argument. Since it is essentially a
> requirement now to use a function to create named functions that can call
> super (disregarding callee), it would be nice if defining that was
> simplified. So something like:
>
> var A = Class.create({
> nifty: function(){}
> });
>
> var B = Class.create(A, function(){
> function nifty(){
> nifty.$super.call(this);
> }
>
> return {nifty: nifty};
> });

How about simplifying further:

var B = Class.create( A,
function nifty(){
nifty.$super.call(this);
}

, function alsoNifty(){
this.nifty();
}
);

Ie, Class.create takes any number of objects of functions or functions. If
functions, the function name is used.

Allen Madsen

unread,
Sep 8, 2009, 10:27:49 AM9/8/09
to prototy...@googlegroups.com
Jim,

I like your suggestion, except that there would be no way to create private variables and functions that could be used in more than one function. For example, with my suggested method I could do:

Var A = Class.create(function(){
  var privateVar = 0;

  function privateFunction(){}

  function nifty(){
    privateFunction();
    privateVar = 3;
  }

  return {nifty: nifty};
});

I'd much rather see it in this form.

Jim Higson

unread,
Sep 8, 2009, 10:53:31 AM9/8/09
to prototy...@googlegroups.com
On Tuesday 08 September 2009 15:27:49 Allen Madsen wrote:
> Jim,
>
> I like your suggestion, except that there would be no way to create private
> variables and functions that could be used in more than one function. For
> example, with my suggested method I could do:

I suppose if you wanted both you could say named functions get added to the
class as a method, whereas the object result of executing anon functions get
added.

Over complicated?

Robert Kieffer

unread,
Sep 8, 2009, 11:11:54 AM9/8/09
to Prototype: Core
I still have some serious reservations about this patch.

Before I dive into details, though, can someone please tell me why
we're not looking at Dean Edwards old Base2 approach[1], or John
Resig's variation on that[2]? That general approach would seem to
offer a good trade-off between performance and usability.

... and it's that trade-off I have reservations about. If Prototype
is going to replace the current $super technique, it's this tradeoff
that I would like to see a nice balance on. And with all respect to
you, T.J., I don't think your solution has that. There's no question
it is more performant (woot!), but I have serious doubts about it's
overall usability.

The requirement for named function expressions (NFEs) puts a serious
crimp in developers coding style. Having to declare the functions in
one section and then wrap them up in a returned data structure in
another... It's nice that this style works for Prototype, and I
appreciate the advantages, but it's not exactly commonplace (not that
anything is). It'll definitely give more than one developer pause.
Can I politely suggest that maybe you guys are a bit too close to the
code and are taking the learning curve here for granted?

And having to explicitly type the name of each function 3-4 times to
define it will very quickly get tiresome. It makes writing code
harder, and it makes refactoring it harder as well. Which means more
prone to error; there is still no IDE that is able to automatically
and accurately change the name of functions when declared like this.
With all the drum-beating around here about code maintainability, I'm
a little surprised to see this issue treated so lightly.

Most importantly, the syntax for invoking a supermethod, while not
quite as horrid as some others we've seen and discussed, is still not
that great. I mean, come on, close your eyes, take a few deep
breaths, and then look at it...

nifty.$super.call(this, spiffy)

... .vs. the current style:

$super(spiffy)

... or what the Dean Edwards/John Resig style:

this._super(spiffy)

I know, I know, the current style has performance issues. But for a
lot of developers a simpler, less confusing syntax will trump that. I
suspect most will continue to use the old $super scheme, which will
make it much harder to deprecate than you might expect. And if the
old $super scheme is gonna stick around for a while because the new
scheme "ain't good enough", than at the end of the day this new code
is just unnecessary code bloat. Certainly Prototype itself doesn't
need it - the Ajax.* classes and Timed Observer aren't used in
situations where $super is called so often that the incremental
performance improvement will make a difference.

Sorry, I probably should have posted this before you spent your time
implementing your patch. Still... I hope this is a constructive gut
check for folks.

- rwk

[1] http://dean.edwards.name/weblog/2006/03/base/
[2] http://ejohn.org/blog/simple-javascript-inheritance/

T.J. Crowder

unread,
Sep 8, 2009, 11:18:20 AM9/8/09
to Prototype: Core
@Allen,

> For your two suggested additions. I don't think I have ever had a use for
> either.

Thanks.

> One thing I would like to suggest though is that Class.create
> take an object or a function as an argument.

I _really_ like that idea, not least because it seems to me that by
reducing the *seeming* complexity it makes it much easier for relative
novices to adopt using named functions. But unfortunately it would a
further API change, because otherwise Class.create can't tell whether
the first argument is a base class constructor or a function you want
it to call to retrieve the members object:

var X = Class.create(BaseClass);
var Y = Class.create(function() {
function nifty() { ... }
return {nifty: nifty};
});

You see the problem. The API change wouldn't need to be large, it's
just suddenly we're impacting the API more than we otherwise have to,
and my gut reaction to that is usually caution (not least because
unless I caution myself, I get a bit change-happy).

None of which is meant to say A) That I'd be opposed to it if a bunch
of smart people said it was worth it (quite the opposite, in fact); or
B) That I'm not going to *immediately* steal your idea for my own
projects. :-)

FWIW, and there are 18 ways to skin this cat, but if we did anything
I'd prefer to leave Class.create alone, but I'd be really interested
in the idea of providing a new entry point (Class.define, perhaps)
that provides this new functionality and which solves the problem of
how to specify the base class in some clever way. Perhaps a new
thread for this sub-topic?

@Jim,

> Ie, Class.create takes any number of objects of functions or functions. If
> functions, the function name is used.

I see where you're coming from, but FWIW I'm with Allen on this one.
Also, there's no standard way to get the name of a function until
ECMAScript5 (which standardizes the truly outrageous idea that
function instances should have -- gasp! -- a "name" property), and at
the moment although Firefox 3.5, Chrome 2, and Safari 4 all already
have that property, IE7 (at least, haven't tested IE8) and Opera10 do
not.

-- T.J. :-)

On Sep 8, 3:27 pm, Allen Madsen <bla...@gmail.com> wrote:
> Jim,
> I like your suggestion, except that there would be no way to create private
> variables and functions that could be used in more than one function. For
> example, with my suggested method I could do:
>
> Var A = Class.create(function(){
>   var privateVar = 0;
>
>   function privateFunction(){}
>
>   function nifty(){
>     privateFunction();
>     privateVar = 3;
>   }
>
>   return {nifty: nifty};
>
> });
>
> I'd much rather see it in this form.
>
> Allen Madsenhttp://www.allenmadsen.com

T.J. Crowder

unread,
Sep 8, 2009, 11:44:19 AM9/8/09
to Prototype: Core
Hi Robert,

Thanks for your thoughts on this.

> The requirement for named function expressions (NFEs) puts a serious
> crimp in developers coding style.

This mechanism does _not_ require NFEs. You can use anonymous
functions and call the super via arguments.callee if you like, and
still get big performance improvement (and stop relying on function
decompilation, although you lose ECMA5 strict mode compliance). The
invocation syntax if you use anonymous functions is ugly enough:

var Thingy = Class.create({

nifty: function(arg) {

arguments.callee.$super.call(this, arg);
}
});

...that I would (in fact, did) suggest a helper:

var Thingy = Class.create({

nifty: function(arg) {

this.callSuper(arguments, arg);
}
});

> Before I dive into details, though, can someone please tell me why
> we're not looking at Dean Edwards old Base2 approach[1], or John
> Resig's variation on that[2]?

We should definitely consider various options. This started because I
sort of accidentally went down a road, found what I thought was a
really nifty solution, and posted asking whether others were
interested in it.

My issue with Resig's approach would be the number of introduced
closures and the slippery _super method, which has too much magic in
it for my taste. These things are subjective. It also won't perform
as well, but we're talking a small increment, and as you say trade-
offs come into play.

> I mean, come on, close your eyes, take a few deep
> breaths, and then look at it...
>
> nifty.$super.call(this, spiffy)

There are lots of constructs that look a bit alien at first. To me
this isn't one of them (and I'm no JavaScript master) but I totally
recognise that for some novices there's going to be a certain amount
of "just do it like this, eventually you'll understand why." That's
true of lots of things. A novice who doesn't know about Function#call
needs to be taught about Function#call, in my view.

If we provide a helper, it could accept either 'arguments' (for
anonymous functions) or the function instance (for named fucntions),
so this:

nifty.$super.call(this, spiffy);

...could be:

this.callSuper(nifty, spiffy);

...if we think that's more helpful. The cost is an extra function
call, which compared with the various other savings we're making isn't
a big deal.

Thanks again for jumping into this,
--
T.J. Crowder
tj / crowder software / com
www.crowdersoftware.com

Allen Madsen

unread,
Sep 8, 2009, 12:00:17 PM9/8/09
to prototy...@googlegroups.com
@TJ
I don't think it would be hard to tell the difference between a base
class construct and a function. You would just need some extra work in
construction of the base class. After the construction of klass, you
could set a property so that it is identifiable. For example:

function klass() {
      this.initialize.apply(this, arguments);
}
if( !klass.name ) klass.name = 'klass';

Then you could do:

if(Object.isFunction(value)){
if(value.name === 'klass'){
//Do base class stuff
} else {
//Do function generator
}
}

I hope its clear where these snippets relate to the source.

Allen Madsen
http://www.allenmadsen.com

T.J. Crowder

unread,
Sep 8, 2009, 12:25:43 PM9/8/09
to Prototype: Core
Hi Allen,

> After the construction of klass, you
> could set a property so that it is identifiable.

Doh! Of course, since we only allow deriving from classes created via
Class.create. I wouldn't go with "name", though, since the
ECMAScript5 spec actually defines "name" for functions. But
_createdByPrototype would work (and echos the _extendedByPrototype
property on extended Elements)...

Gets my vote, as you say, makes the syntax much more like several
other things we do:

var Thingy = Class.create(function(){
// Define
function nifty() { ... }
function spiffy() { ... }
function keen() { ... }

// Export public methods
return {nifty: nifty, spiffy: spiffy, keen: keen};
});

...while still accepting the form where we've already been given an
object.

Nice one!
--
T.J. Crowder
tj / crowder software / com
www.crowdersoftware.com


On Sep 8, 5:00 pm, Allen Madsen <bla...@gmail.com> wrote:
> @TJ
> I don't think it would be hard to tell the difference between a base
> class construct and a function. You would just need some extra work in
> construction of the base class. After the construction of klass, you
> could set a property so that it is identifiable. For example:
>
> function klass() {
>       this.initialize.apply(this, arguments);}
>
> if( !klass.name ) klass.name = 'klass';
>
> Then you could do:
>
> if(Object.isFunction(value)){
>   if(value.name === 'klass'){
>     //Do base class stuff
>   } else {
>     //Do function generator
>   }
>
> }
>
> I hope its clear where these snippets relate to the source.
>

Robert Kieffer

unread,
Sep 8, 2009, 2:43:40 PM9/8/09
to Prototype: Core
On Sep 8, 8:44 am, "T.J. Crowder" <t...@crowdersoftware.com> wrote:
>             arguments.callee.$super.call(this, arg);
>             this.callSuper(arguments, arg);

"six of one, half-dozen of the other"... but I suppose there is no
perfect solution. Still, it's hard to swallow something this
convoluted when you've gotten used to $super (or this._super() ) :-P

> My issue with Resig's approach would be the number of introduced
> closures and the slippery _super method, which has too much magic in
> it for my taste. These things are subjective.  It also won't perform
> as well, but we're talking a small increment, and as you say trade-
> offs come into play.

I have to confess it's been long enough since I looked at that code
that I don't have an opinion about it. I do know that it performance
tested well, and I definitely prefer the invocation syntax.

Honestly, at this point I'm mostly worried that that we may get
enamored with a solution that is different, but not necessarily all
that much "better". More performant, yes, but at the cost of
usability. I'm tempted, even, to argue for the removal of supermethod
invocation altogether in Prototype. It's not like it actually needs
it - there are a couple places where $super is used, but it would be
trivial to rewrite that to where it's not necessary. Developers would
benefit from a smaller Prototype codebase, and they code pick and
choose the supermethod technique that best met their needs, and import
the appropriate script for that.

That notion is both heretical and ironic I suppose, since I guess I
started the whole "look how shitty Prototype supermethod performance
is!" thread. But history has taught me that developers who write code
without having a concrete use for it very rarely make the right
choices. It's much better to let requirements drive the code choices,
rather than the other way around.

Okay, climbing down off my soapbox now to go get lunch. :-)

- rwk

Allen Madsen

unread,
Sep 8, 2009, 3:00:31 PM9/8/09
to prototy...@googlegroups.com
Robert,

I use super heavily in many of my applications and it isn't something
trivial to just add it on to prototype's class. I would be very
against it. As long as it is clearly documented then I don't see a
problem. I spend a good deal of time in #prototype and a majority of
the questions are around selector's or the AJAX stuff. From my
experience most newbies don't even use Class.

Allen Madsen
http://www.allenmadsen.com

T.J. Crowder

unread,
Sep 8, 2009, 4:21:33 PM9/8/09
to Prototype: Core
Hi Robert,

> > arguments.callee.$super.call(this, arg);
> > this.callSuper(arguments, arg);
>
> "six of one, half-dozen of the other"...

Um, "this.callSuper(arguments, arg)" (or "this.callSuper(nifty, arg)")
is precisely *one* argument more than Resig's "this._super(arg)", but
without the cost of the closures and unnecessary, misleading (IMV)
magic. It's a lot clearer than the raw syntax needed with anonymous
functions. "Six of one, half-dozen of the other" seems a bit harsh.

> Honestly, at this point I'm mostly worried that that we may get
> enamored with a solution that is different, but not necessarily all
> that much "better". More performant, yes, but at the cost of
> usability...

That's interesting. I started down this road precisely because I
found the existing API flawed; the performance and compliance aspects
came later. I'm not at all interested in replacing things without a
compelling reason, we all have better things to do.

> I'm tempted, even, to argue for the removal of supermethod
> invocation altogether in Prototype.

FWIW, I think that would be a mistake (but then, I use supercalls and
had a concrete reason for going down this path). Not only because
it's a useful feature, but also because it's one of the things people
of a certain background look for in a JavaScript library.

I didn't realize you'd done a "crappy performance in supercalls"
thrread (I was away from this group for a while). I'd seen the
excellent work you did with curried and bound functions, though, and
I'm glad to hear that will be finding its way into 1.6.1.

-- T.J. :-)

kangax

unread,
Sep 8, 2009, 5:23:26 PM9/8/09
to Prototype: Core
On Sep 8, 12:25 pm, "T.J. Crowder" <t...@crowdersoftware.com> wrote:
> Hi Allen,
>
> > After the construction of klass, you
> > could set a property so that it is identifiable.
>
> Doh!  Of course, since we only allow deriving from classes created via
> Class.create.  I wouldn't go with "name", though, since the
> ECMAScript5 spec actually defines "name" for functions.  But
> _createdByPrototype would work (and echos the _extendedByPrototype
> property on extended Elements)...

T.J., could you point me to the exact section specifying `name` on
Function objects? I don't remember seeing it in ES5.

[...]

--
kangax

Tobie Langel

unread,
Sep 8, 2009, 5:48:47 PM9/8/09
to Prototype: Core
Allen, You can do this already pretty easily.

Just do:

Var A = Class.create(function(){
var privateVar = 0;
function privateFunction(){}
function nifty(){
privateFunction();
privateVar = 3;
}
return {nifty: nifty};
}());

Just note that in both cases, those private variables are `class`
variables
shared between all instances.

kangax

unread,
Sep 8, 2009, 5:53:01 PM9/8/09
to Prototype: Core


On Sep 8, 11:11 am, Robert Kieffer <bro...@gmail.com> wrote:
> I still have some serious reservations about this patch.
>
> Before I dive into details, though, can someone please tell me why
> we're not looking at Dean Edwards old Base2 approach[1], or John
> Resig's variation on that[2]?  That general approach would seem to
> offer a good trade-off between performance and usability.

Last time I checked, Resig's implementation relied on function
decompilation too, which we are trying to avoid this time.

>
> ... and it's that trade-off I have reservations about.  If Prototype
> is going to replace the current $super technique, it's this tradeoff
> that I would like to see a nice balance on.  And with all respect to
> you, T.J., I don't think your solution has that. There's no question
> it is more performant (woot!), but I have serious doubts about it's
> overall usability.
>
> The requirement for named function expressions (NFEs) puts a serious
> crimp in developers coding style.  Having to declare the functions in

Actually, there's no such requirement. You are free to use anonymous
function expressions, named function expressions or function
declarations. You only need to have a reference to a function; doesn't
matter how you created it and which identifier you gave to it.

var f;
Class.create({
initialize: (f = function(){
// ...
})
});

There's your `f`, referencing function in question. No Identifier
needed.

> one section and then wrap them up in a returned data structure in
> another... It's nice that this style works for Prototype, and I
> appreciate the advantages, but it's not exactly commonplace (not that
> anything is).  It'll definitely give more than one developer pause.
> Can I politely suggest that maybe you guys are a bit too close to the
> code and are taking the learning curve here for granted?
>
> And having to explicitly type the name of each function 3-4 times to
> define it will very quickly get tiresome.  It makes writing code
> harder, and it makes refactoring it harder as well.  Which means more

I agree. Repetition of function names is by far the biggest downside
to this approach. And, of course, it is impossible to not repeat
function name without either relying on function decompilation or
completely hog performance by blindly wrapping every single method
with a closure to its parent.

[...]

--
kangax

Tobie Langel

unread,
Sep 8, 2009, 5:53:35 PM9/8/09
to Prototype: Core

> T.J., could you point me to the exact section specifying `name` on
> Function objects? I don't remember seeing it in ES5.

That's because it's not part of that spec. If I remember correctly it
was discussed for Harmony.

Best,

Tobie

Robert Kieffer

unread,
Sep 8, 2009, 6:02:20 PM9/8/09
to prototy...@googlegroups.com
On Tue, Sep 8, 2009 at 1:21 PM, T.J. Crowder <t...@crowdersoftware.com> wrote:

Hi Robert,

> >             arguments.callee.$super.call(this, arg);
> >             this.callSuper(arguments, arg);
>
> "six of one, half-dozen of the other"...

Um, "this.callSuper(arguments, arg)" (or "this.callSuper(nifty, arg)")
is precisely *one* argument more than Resig's "this._super(arg)", but
without the cost of the closures and unnecessary, misleading (IMV)
magic.  It's a lot clearer than the raw syntax needed with anonymous
functions.  "Six of one, half-dozen of the other" seems a bit harsh.

I apologize, I was being glib (hence the quotes.)  But there is a grain of truth there.  Both these incantations are unintuitive in their own way.

callSuper may have only one more argument but it's a wierd one for sure.  Especially for developers unfamiliar with 'arguments' and its 'callee' property.  Many devs will treat this as yet another piece of JS voodoo code they have to remember - "you do it just because".  And what if the super method takes different arguments than the sub method, "Wait a minute, why am I passing an array with 'foo' & 'bar' in it, when I only want to hand 'baz'  to the supermethod?"

> Honestly, at this point I'm mostly worried that that we may get
> enamored with a solution that is different, but not necessarily all
> that much "better".  More performant, yes, but at the cost of
> usability...

That's interesting.  I started down this road precisely because I
found the existing API flawed;

Was it the API you found flawed, or the implementation of that API?  I think the $super argument is a rather elegant solution.  Non-standard, sure, and lacking performance, but conceptually elegant.  In essence: "If you need to access $super, just pass it to yourself as the first argument."  It's easy to remember, and hard to screw up.

I didn't realize you'd done a "crappy performance in supercalls"
thrread (I was away from this group for a while).  I'd seen the
excellent work you did with curried and bound functions, though, and
I'm glad to hear that will be finding its way into 1.6.1.

It wasn't a thread in this group but, rather, the blog post I did a while back that got some attention on Ajaxian.com and that other folks ran with for a bit on their own blogs:

http://ajaxian.com/archives/javascript-inheritance-performance

T.J. Crowder

unread,
Sep 9, 2009, 3:44:41 AM9/9/09
to Prototype: Core
@kangax, @tobie:

*sigh* The "name" property _was_ in the November '08 draft (sect.
15.3.5.4), I didn't realize they'd taken it out again, but apparently
they have, it's not in the April candidate spec (which has now
replaced the November draft in my reference folder).

@kangax:

> Last time I checked, Resig's implementation relied on function
> decompilation too, which we are trying to avoid this time.

You're right, at least the version linked by Robert does. I missed
that initially because it's an implicit conversion when passing the
function into a regex. It also happily assumes that if "_super"
appears anywhere in the method -- inside a string, as a variable name,
whatever -- that you want the supercall magic wrapped around it.
Finally, it tests if function decompilation works and if it doesn't,
the implementation wraps the magic around *all* methods.

@Robert:

> > That's interesting. I started down this road precisely because I
> > found the existing API flawed;
>
> Was it the API you found flawed, or the implementation of that API?

Initially it was the API, I didn't like passing the superfunction in
as an argument. That lead to my looking at the implementation behind
it.

-- T.J. :-)

Jim Higson

unread,
Sep 9, 2009, 4:02:28 AM9/9/09
to prototy...@googlegroups.com
On Tuesday 08 September 2009 16:18:20 T.J. Crowder wrote:
> @Jim,
>
> > Ie, Class.create takes any number of objects of functions or functions.
> > If functions, the function name is used.
>
> I see where you're coming from, but FWIW I'm with Allen on this one.
> Also, there's no standard way to get the name of a function until
> ECMAScript5 (which standardizes the truly outrageous idea that
> function instances should have -- gasp! -- a "name" property), and at
> the moment although Firefox 3.5, Chrome 2, and Safari 4 all already
> have that property, IE7 (at least, haven't tested IE8) and Opera10 do
> not.

I have a hunch we could get function names in the same way that we implement
Function#argumentNames. A regex on the toString.

Jim Higson

unread,
Sep 9, 2009, 4:28:06 AM9/9/09
to prototy...@googlegroups.com
On Wednesday 09 September 2009 09:02:28 Jim Higson wrote:

> > I see where you're coming from, but FWIW I'm with Allen on this one.
> > Also, there's no standard way to get the name of a function until
> > ECMAScript5 (which standardizes the truly outrageous idea that
> > function instances should have -- gasp! -- a "name" property), and at
> > the moment although Firefox 3.5, Chrome 2, and Safari 4 all already
> > have that property, IE7 (at least, haven't tested IE8) and Opera10 do
> > not.
>
> I have a hunch we could get function names in the same way that we
> implement Function#argumentNames. A regex on the toString.

I should have looked first - this is function decompilation and considered a
bad thing.

T.J. Crowder

unread,
Sep 9, 2009, 7:29:21 AM9/9/09
to Prototype: Core
Hi all,

I thought of a wrinkle today: Mixins. This new mechanism modifies
function instances if they override base class functions (it leaves
them alone if they don't). So if you mix in something that has a
function with the same name as a parent class's function, we will set
a $super property on the mixin function's instance. That seems wrong
to me, mixins should be read-only as far as the mechanism is
concerned, IMHO.

Now, in _practical_ terms, unless the mixin function calls $super
(which seems an odd thing for a mixin to do, but I can come up with an
edge case use case for it), it doesn't matter much. But if A) the
mixin function does (presumably optionally) call $super, and B) more
than one class mixes the mixin in, AND C) the mixin overrides one of
the functions in at least one of those classes, you get class
crosstalk -- a very bad thing.

My first thought for how to deal with this was stupid and I'll spare
you.

My *second* (hopefully not stupid) thought was to mark mixin functions
with a property telling us to leave them alone. I see two
ramifications to doing that:

1. An API change: To define a mixin, you'd ideally want to run it
through something (something we'd presumably provide) that spins
through and marks the functions so we leave them alone.

var MyMixIn = Class.createMixin(...);

2. Mixins can't even optionally participate in supercalls, which they
can with the current mechanism.

Now, for me, #2 is not a problem. I'm not thrilled about introducing
#1, although really you only have to do that to protect against an
edge case you're probably not going to run into. It's someting a
library of mixins would want to be sure to do, but within a non-
library project, not sure it really matters.

-- T.J.

Allen Madsen

unread,
Sep 9, 2009, 10:31:52 AM9/9/09
to prototy...@googlegroups.com
TJ,

I don't particularly think this is a problem or even a new problem
(wouldn't the current let you do this as well?). This sounds like
protecting the developer from themselves. I think as long as you
sufficiently state what will happen in a particular instance then it
is perfectly reasonable to allow each individual developer to handle
it as they see fit.

Allen Madsen
http://www.allenmadsen.com

Robert Kieffer

unread,
Sep 9, 2009, 11:00:11 AM9/9/09
to prototy...@googlegroups.com
I'm with Allen on this.  I don't think it's uncommon for mixins to want to hook into existing functionality.  This is trivial if they can invoke $super (the original object's method), and problematic otherwise.

To give a concrete example, a while ago I implemented a "Selectable" mixin for some collection classes.  Because this mixin maintained the selection state in a private cache, it needed to hook into the remove() method to allow it to properly update the cache.  Thus, I could easily see doing something like this:

var Selectable = Class.createMixin({
  // Various methods we add to the collection class...
  selectItem: function(item){...},
  deselectItem:function(item){...},
  getSelectedItems: function() {...},

  // Make sure we deselect items before they're removed
  remove: function($super, item) {
    this.deselectItem(item);
    $super(item);
  }
})

T.J. Crowder

unread,
Sep 9, 2009, 11:04:06 AM9/9/09
to Prototype: Core
Hi Allen,

Thanks. I agree, it's a very edgy edge case. :-)

> (wouldn't the current let you do this as well?)

Actually, it works in the current implementation, because if a mixin
function uses $super (which again is a bit odd), addMethods wraps it
up in a closure and never modifies the actual function. The closure
is specific to the subclass, and so it all Just Works.

-- T.J.

On Sep 9, 3:31 pm, Allen Madsen <bla...@gmail.com> wrote:
> TJ,
>
> I don't particularly think this is a problem or even a new problem
> (wouldn't the current let you do this as well?). This sounds like
> protecting the developer from themselves. I think as long as you
> sufficiently state what will happen in a particular instance then it
> is perfectly reasonable to allow each individual developer to handle
> it as they see fit.
>
> Allen Madsenhttp://www.allenmadsen.com

kangax

unread,
Sep 9, 2009, 12:10:02 PM9/9/09
to Prototype: Core


On Sep 9, 7:29 am, "T.J. Crowder" <t...@crowdersoftware.com> wrote:
> Hi all,
>
> I thought of a wrinkle today: Mixins. This new mechanism modifies
> function instances if they override base class functions (it leaves
> them alone if they don't). So if you mix in something that has a
> function with the same name as a parent class's function, we will set
> a $super property on the mixin function's instance. That seems wrong
> to me, mixins should be read-only as far as the mechanism is
> concerned, IMHO.
>
> Now, in _practical_ terms, unless the mixin function calls $super
> (which seems an odd thing for a mixin to do, but I can come up with an
> edge case use case for it), it doesn't matter much. But if A) the
> mixin function does (presumably optionally) call $super, and B) more
> than one class mixes the mixin in, AND C) the mixin overrides one of
> the functions in at least one of those classes, you get class
> crosstalk -- a very bad thing.
>
> My first thought for how to deal with this was stupid and I'll spare
> you.
>
> My *second* (hopefully not stupid) thought was to mark mixin functions
> with a property telling us to leave them alone. I see two
> ramifications to doing that:

Well, technically mixins are already "marked" by being an object,
rather than a function :)

// Constructor/"Class" (a function)
function Person(){}

// Mixin (not a function, although it could be (!))
var Observable = {
observe: function(){ ... },
stopObserving: function(){ ... }
}

I don't think there was ever such requirement for mixins in Prototype,
though, so there's a chance people are using function-based mixins
(for whatever reasons):

function Observable(){ ... };

Object.extend(Observable, {
observe: function(){ ... },
stopObserving: function(){ ... }
});

[...]

--
kangax

T.J. Crowder

unread,
Sep 9, 2009, 6:52:51 PM9/9/09
to Prototype: Core
Hi Robert,

That's a very interesting use case, thanks for that. I can certainly
see the utility of that, and in fact as I was writing all of this up
for my (pathetic little) blog I was getting increasingly uncomfortable
with the mixin issue.

If it's important for mixins to participate in hierarchy, it occurs to
me that my second point (saying they can't) is unduly pessimistic. Of
course they can. Unlike subclass methods, it's important that we
don't modify them directly, but that doesn't mean they can't play. In
this one special case where we basically have no choice, we can allow
ourselves the indirection and wrap them, modifying the wrapper rather
than the original. That costs a closure per mixin method (not on
every call, just at mixin-time), and then an extra #apply on each
mixin call. Still much more efficient than what we currently have,
and it keeps mixins fully able to participate in the hierarchy.

That's much more satisfying that shutting them out (and potentially
breaking the chain).

Thanks again,

-- T.J.
Reply all
Reply to author
Forward
0 new messages