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.