three small proposals: the bikeshed cometh!

Alex Russell alex at dojotoolkit.org
Thu Apr 29 00:25:32 PDT 2010


Some small, pre-colored panels for the shed. Given that these are  
mostly matters of syntax and not semantics, please believe me when I  
suggest that the warts discussed herein present sharp edges that  
should be rounded off by the committee -- not because they're  
interesting in any way but because the primary users of the language  
are shipping (bad) fixes around the network billions of times a day.  
Such rank inefficiency needs sunset date.

Summary of proposals:
---------------------

  // 1.) Really generic generics

  ArrSubtype = function() { };
  ArrSubtype.prototype = Object.create(Array.prototype);

  var nas = new ArrSubtype();
  nas.push("howdy", "pardner");

  nas.slice(0) instanceof Array; // currently true
  nas.slice(0) instanceof ArrSubtype; // will be true after fix

  // 2.) Shorthand for "function", aka the "not lambda"

  node.addEventListener("click", #(e){ e.preventDefault(); });
  node.removeEventListener("click" obj!#(e){ ... }); // see #3

  // 3.) Shorthand for Function.prototype.bind

  var boundMethod = obj!method;
  node.addEventListener("click", obj!method);
  node.removeEventListener("click", obj!method);


Discussion:
-----------

1.) Really generic generics

Methods on Array.prototype that today return instances of Array should  
instead return instances of whatever subtype begat them. Assume that  
the DOM sucked less and that NodeList instances were subtypes of  
Array. In that case, we should be able to do something like:

  NodeList.prototype.parents = function() {
     // should return a NodeList
      return this.map(function(n) { return n.parentNode; });
  }

  document.querySelectorAll("p").slice(2).parents().filter(...);

Today, the only way for subtypes to use these generics is to wrap or
re-implement them. The functions that need to be fixed include:

  map, filter, splice, slice, concat

Rationale:

  * Kill code in libraries that exists only to wrap built-in methods  
thanks to existing mis-specification of generics
  * Sub-types of Array suffer many warts, but making Array.prototype  
methods return instances of subtypes will allow DOM-side changes to  
make subtyping much more natural in real-world systems

2.) Function shorthand

The most commonly-used object types in JavaScript have syntactic sugar:

  new Array(...) == [ ... ]
  new Object(); ... == { .... }

Why leave functions out of the party? Lambda syntaxes have been  
discussed at great length with most of them either failing to remove  
the "function" wart or giving up multi-line or visual delineation  
properties. We instead propose just shortening the word "function".  
Several variants are possible:

   // equivalent:
   function(){ return 10; }
   #(){ return 10; }
   #{ return 10; } // no args, optionally elide ()

   // lambda-style return
   #{ 10; }

   // makes for easy-to-read operations:
   [ ... ].map(#(i){ i+10; });

Rationale:

  * less typing, particularly when supplying Array generic and DOM  
callbacks
  * equivalent semantics to the function(){} syntax
  * able to work as a short-ish lambda-like declaration without large  
changes
  * "JavaScripty"; keeps the existing brace rules, keeps multi-line  
behavior, and helps to visually delimit start/end of function object.

3.) Syntax for bound function de-reference

Many functions, both in the DOM and in the library, accept functions  
as arguments. ES5 provides a bind() method that can ease the use of  
these functions when the passed method comes from an object. This is,  
however, inconvenient. E.g.:

  node.addEventListener("click", obj.method.bind(obj, ...));

Also, insufficient. Event listeners can't be detached when using  
Function.prototype.bind:

  // doesn't do what it looks like it should
  node.removeEventListener("click", obj.method.bind(obj, ...));

So why does this suck? Two reasons: it's long-ish to type, and it  
doesn't do what the dot operator does -- i.e., return the same  
function object every time.

What to do? We propose the bang operator -- ! -- as a new form of  
binding de-reference of function objects. It's one character and even  
includes a dot.  It has the following additional properties:

  1.) all de-references via bang from an object/function pair return  
the *same* function object (modulo #4).
  2.) non-function objects de-referenced via bang are not mangled in  
any way (identical to dot-operator de-reference)
  3.) there's no provision for binding arguments (curry)
  4.) bang-bound functions are weakly referenced. If GC would  
otherwise remove a function object from memory, having previously bang- 
bound a function should not keep the function object alive

As from the intro:

  node.addEventListener("click", obj!method);
  // actually removed without extra book-keeping or wrappers!
  node.removeEventListener("click", obj!method);

And a small (untested) version of the behavior as a function written  
in ES5 (without weakref behavior):

  var bang = function(obj, func) {
    var fn = obj[func];
    // Still no isFunction...*sigh*
    if (Object.prototype.toString.call(fn) === "[object Function]") {
      if (!obj["_bound"]) {
        Object.defineProperty(obj, "_bound", {
          value: {},
          enumerable: false,
          configurable: false,
          writable: false
        });
      }
      var bound = obj._bound[func];
      if (!bound) {
        bound = obj._bound[func] = fn.bind(obj);
      }
      return bound;
    } else {
      // not a function object, just return the deref
      return fn;
    }
  };
  // ES3 impl lacks nice syntax:
  node.addEventListener("click", bang(obj, "method"));
  // works!
  node.removeEventListener("click", bang(obj, "method"));

Thoughts?


More information about the es-discuss mailing list