Simple Lists with ES6/Harmony Proxies

If you are into meta programming proxies are the most outstanding addition to JavaScript. Together with apply(), call() they allow to nicely abstract from build-in features and help to create domain specific languages.

I was in need of simple lists spending a vocabulary like head, tail.

var lst = list(1, 2, 3, 4);
lst.head  // 1
lst.tail  // 2, 3, 4

Before ES6 you had two choices either extend the prototype of arrays (dangerous) or code your own library. With proxies you can avoid the former and simplify the latter. Here’s the first, second, third attempt:

function list(){
  var 
    ap     = Array.prototype,
    arr    = ap.slice.call(arguments),
    copy   = Array.apply.bind(Array, Array, arr),
    slice  = ap.slice.bind(arr),
    concat = ap.concat.bind(arr),
    multiply = function(m){
      return concat.apply(null, 
        Array.apply(null, {length: m -1}).map(() => arr));
    };
  return new Proxy(arr, {
    get: function(proxy, name){
      return (
        proxy[name] !== undefined ? proxy[name] : 
        name === "nil"      ? !proxy.length :
        name === "head"     ? list.apply(null, slice(0, 1)) :
        name === "tail"     ? list.apply(null, slice(1)) :
        name === "last"     ? list.apply(null, slice(-1)) :
        name === "inverse"  ? list.apply(null, copy().reverse()) :
        name === "multiply" ? function(m){
          return list.apply(null, multiply(m));} :
        name === "append"   ? function(){
          return list.apply(null, concat(ap.slice.call(arguments)));} :
        name === "prepend"  ? function(){
          return list.apply(null, 
            ap.slice.call(arguments).concat(proxy));} :
        name === "string"   ? "[list " + proxy.join(", ") + "]" :
          null
      );
    }
  });  
}

Basically proxies intercept any access to the proxied object by defining traps. The get trap of the handler above jumps in whenever a property is accessed and gives you the proxy and the name of the accessed property.

// Examples
a = obj.prop      // name: prop
b = obj['test']   // name: test
c = obj.func()    // name: func

There are more traps documented at Mozilla’s MDN. Sadly you can’t overload operators, so still you won’t improve this a lot:

[1, 2] + [3, 4]   // -> "1,23,4"

Back to the list you see defining methods and attributes is much simpler compared to using Object.defineProperty(). Also the native array functions are still available and may even precede the handler. With the idiom above you have fine control which property gets routed to the handler or the array.

list(1, 2, 3, 4).length // -> 4

The handler mostly returns lists again and method concatenation is a snap:

list(1, 2, 3, 4).tail.head.prepend(0, 1) // -> 0, 1, 2

Go ahead and use proxies to make your JavaScript more readable.

 
9
Kudos
 
9
Kudos

Now read this

Milestone: Villages

Almost one year ago I started on this project. It still looks promising, I’ve found work-arounds for all road blocks and nearly all abstractions are in place and working. That means I from now I build on code I’ve written myself. No one... Continue →