super, methods, constructors & Co.

Allen Wirfs-Brock allen at wirfs-brock.com
Fri Jun 1 01:04:41 PDT 2012


On May 31, 2012, at 10:57 PM, Luke Hoban wrote:

> On May 31, 2012, at 1:54 PM, Allen Wirfs-Brock wrote:
> 
>> On May 31, 2012, at 1:53 AM, Herby Vojčík wrote:
> 
>>> Hello,
>>> 
>>> reacting to "super only in classes", I'd like to propose widening it just a little bit:
>>> 
>>> 1. Allow super in every concise methods (in classes, as well as out of them).
> 
>> Basically this means in object literals (or object extension literals if we have them)
> 
> It still seems to me that the majority of the time, super used in an object literal will not actually behave correctly.  My pushback on a general notion of super outside of classes is based on the assumption that it will be a misleading feature, suggesting support for super calls in many places there actually is not.  
> 
> In fact, without <|, are there any cases where super behaves correctly in an object literal, other than making super calls to Object.prototype functions?

We haven't eliminated the ability to define object literals that inherit from objects other than Object.prototype.  We have just changed the syntax for specifying them from:
   proto <| {}
to
   {__proto__: proto}

In general, if you have multiple level instance based inheritance hierarchies, super has the same general utility that it has in an a class/instance based inheritance hierarchy. 

> 
> Take Backbone/Underscore as an example.  A developer writes some code like:
> 
> var Note = Backbone.Model.extend({
>    set: function(attributes, options) {
>        super.set(attributes, options);
>    }
> });

Of course, the above is a case where I suggest a mustache should probably be used.  If extend did not explicitly deal with rebinding super referencing methods (which it can't, if we eliminate defineMethod) then the set method would throw when it tried to invoke the super.set.  So, the code fails with an exception rather than silently working incorrectly.

Also, a very similar issue will apply to if the argument to extend had any private name properties.  So this isn't a problem that is specific to super. I can actually envision how it may be easier for a built-in extend function to handle super rebinding than it would be for it to handle private names.  I don't currently see how a non-built-in Es<=5 semantics based extend could handle it.

The issue of passing new semantic constructors into legacy frameworks is certainly worth thinking about.  Although, i tend to think that actively maintain frameworks will get ahead of the ES6 adoption curve.  And that legacy using a out of date framework is much less likely to start using new ES6 constructs.

> 
> They rightly think they can use a super call inside an object literal.   But it doesn't behave correctly, because extend is ultimately (somewhere in code that the end-developer shouldn't really need to be aware of) implemented in just the simple ES3-compatible way:
> 
>  _.extend = function(obj) {
>    each(slice.call(arguments, 1), function(source) {
>      for (var prop in source) {
>        obj[prop] = source[prop];
>      }
>    });
>    return obj;
>  };
> 

Of course, the above doesn't even work for ES5 since it it ignores non-enumerable properties, doesn't copy accessor properties correctly, and probably unintentionally includes inherited properties. We should be cherry picking super as the only feature that is problematic for such code.

One might speculate if these deficiencies aren't causing problems today, then perhaps the issue of passing new language constructs into to legacy frameworks that don't correctly deal with them is not a significant problem.

> As I understand it, super will not work correctly in cases like these unless the authors of every object model library opt in to taking an ES6 dependency on Object.defineMethod, or fork their implementations to detect and behave differently on ES6.  I believe the necessary change to these APIs is intended to be something like this - but I'm pretty sure this also wouldn't be correct, because it rebinds the super bindings of functions which may have been correctly bound previously to other points in the prototype hierarchy. 
> 
>  _.extend = function(obj) {
>    each(slice.call(arguments, 1), function(source) {
>      for (var prop in source) {
>        if(typeof prop === 'function') {
>          Object.defineMethod(obj, prop, source[prop]);
>        } else {
>          obj[prop] = source[prop];
>        }
>      }
>    });
>    return obj;
>  };\
Basically, except I would only want to rebind the function if the current function is bound to the object it is being extracted from.
> 
> Basically - I just don't see that this feature can be made to feel like it "just works", in a practical sense, outside of classes.

But also note that class instance or class prototype objects can be used in all the same situations where you have used object literals.  Nothing here is unique about their occurrence in object literals.  

Here is another way to think about this class of issues. super is a language feature that is design to work at the level of the built-in behavior composition mechanism of ES (a fixed single inheritance [[Prototype]] chain).  _extend and other abstraction impose a different behavioral composition model.  super may not work as expected with those other models unless their implementation takes it input account in their implementation.

Thanks, for these issue.  They are good ones.

Allen


> 
> Luke
> 



More information about the es-discuss mailing list