OSCON - ES6 metaprogramming unleashed
-
Upload
javier-arias-losada -
Category
Technology
-
view
972 -
download
2
Transcript of OSCON - ES6 metaprogramming unleashed
Javascript metaprogramming?
Metaprogramming is powerful and fun, but remember:
"With great power comes great responsibility"
about me
Javier Arias, senior software engineer at Telefonica. Technology and software development lover.
@javier_arilos
http://about.me/javier.arilos
metaprogramming
“The key property of metaprograms is that
manipulate other programs or program
representations.” - Gregor Kiczales
Meta level vs Base level
metaprogramming in real life
Compiler/transpilers: gcc, coffeescript…
Macro languages (eg. C preprocessor)
Using "eval" to execute a string as code
Database scaffolding/ORM: mongoose, …
IDEs: Eclipse, …
reflective metaprogramming
A program that metaprograms itself -
This is the subject of the talk!
Reflective Metaprogramming in JS
MMM… very interesting … is there any JS?
JS metaprogramming up to ES5object metaprogramming API
- Good
function metaprogramming
- Ugly
eval
- Bad
The Good: Object metapgrming API● modify property access:
○ getters & setters
○ property descriptors
● Object mutability:
preventExtensions,
seal, freeze
Obj metaprogramming: Test Spy
Test Spy myFunction
[1] myFunction = spy (myFunction)
[5] assert eg. calledOnce
[2] myFunction(1, ‘a’)
Test spy is a function that records calls to a spied function - SinonJS
[3] store call [4] call
Obj metaprogramming: Test Spy
function functionSpy(func){
Object.defineProperty(proxyFunc, "_callCount", {value: 0, writable: true});
Object.defineProperty(proxyFunc, "once", {get: function(){return this._callCount==1});
Obj metaprogramming: Test Spy
function functionSpy(func){
Object.defineProperty(proxyFunc, "_callCount", {value: 0, writable: true});
Object.defineProperty(proxyFunc, "once", {get: function(){return this._callCount==1});
var proxyFunc = function () { //intercept and count calls to func
proxyFunc._callCount += 1;
return func.apply(null, arguments);
};
return proxyFunc;
}
The Bad: eval
avoid using eval in the browser for input from the user or your
remote servers (XSS and man-in-the-middle)
“is sometimes necessary, but in most cases it
indicates the presence of extremely bad coding.”
- Douglas Crockford
var remainder = new Function('a', 'b', 'return a % b;');
remainder(5, 2); // 1
function constructor
Create functions from Strings…
Similar to eval but differences in scope.
function reflection - length
Function.length: number of parameters of a
function.
Usage example: Express checking middlewares signature
function getParameters(func) { //The regex is from Angular
var FN_PARAMS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m;
var params = func.toString().match(FN_PARAMS)[1].split(',');
return params;
}
function parameters reflection
ES6 and Proxy
The Proxy can define custom behavior for
fundamental operations.
➔ property lookup
➔ assignment
➔ enumeration
➔ (...)
Proxy explained
handler: interceptor. traps per operation.
proxy &
handlertarget
A Proxy wraps a target object.
target: proxied object.
proxy sample: noSuchPropertyze
var myObj = {
a: 1,
b: 'nice'
};
myObj = noSuchPropertyze(myObj); // We want to INTERCEPT access to properties (get)
myObj.b; // nice
myObj.nonExistingProperty; // Error
function noSuchPropertyze(obj) {
var handler = {
get: function(target, name, receiver){
if(name in target) return target[name];
throw new Error('Not found:' + name);
}
};
return new Proxy(obj, handler);
}
var myObj = noSuchPropertyze({a: 1, b: 'nice'});
myObj.b; // nice
myObj.nonExistingProperty; // Error
proxy sample: noSuchPropertyze
proxy &
handler
target
myObj[name]
function noSuchPropertyze(obj) {
var handler = {
get: function(target, name, receiver){
if(name in target) return target[name];
throw new Error('Not found:' + name);
}
};
return new Proxy(obj, handler);
}
var myObj = noSuchPropertyze({a: 1, b: 'nice'});
myObj.b; // nice
myObj.nonExistingProperty; // Error
proxy sample: noSuchPropertyze
proxy &
handler
target
myObj[name]
function noSuchPropertyze(obj) {
var handler = {
get: function(target, name, receiver){
if(name in target) return target[name];
throw new Error('Not found:' + name);
}
};
return new Proxy(obj, handler);
}
var myObj = noSuchPropertyze({a: 1, b: 'nice'});
myObj.b; // nice
myObj.nonExistingProperty; // Error
proxy sample: noSuchPropertyze
proxy &
handler
target
myObj[name]
DSL with Proxies- implementation
// ==== to(3).double.pow.get ===
var to = (function closure() { // closure for containing our context
var functionsProvider = { //Object containing our functions
double: function (n) { return n*2 },
pow: function (n) { return n*n }
};
return function toImplementation(value) { // Magic happens here!
// (...) implementation
return new Proxy(functionsProvider, handler);
}
}());
DSL with Proxies- implementation
// ==== to(3).double.pow.get ===
var to = (function closure() { // closure for containing our context
var functionsProvider = { //Object containing our functions
double: function (n) { return n*2 },
pow: function (n) { return n*n }
};
return function toImplementation(value) { // Magic happens here!
// (...) implementation
return new Proxy(functionsProvider, handler);
}
}());
// ==== to(3).double.pow.get ===
var to = (function closure() { // closure for containing our context
var functionsProvider = { //Object containing our functions
double: function (n) { return n*2 },
pow: function (n) { return n*n }
};
return function toImplementation(value) { // Magic happens here!
// (...) implementation
return new Proxy(functionsProvider, handler);
}
}());
DSL with Proxies- implementation
DSL with Proxies- implementation // ==== to(3).double.pow.get === return function toImplementation(value) {
var pipe = []; //stores functions to be called
var handler =
{ get(target, fnName, receiver) {
if (fnName in target){ //eg. .double : get the function and push it
pipe.push(target[fnName]);
return receiver;} //receiver is our Proxy object: to(3)
if (fnName == "get")
return pipe.reduce(function (val, fn) { return fn(val) }, value);
throw Error('Method: '+ fnName +' not yet supported');
}, set(target, fnName, fn, receiver) {
target[fnName] = fn;} //dynamic declaration of functions
};
return new Proxy(functionsProvider, handler);}}());
DSL with Proxies- implementation // ==== to(3).double.pow.get === return function toImplementation(value) {
var pipe = []; //stores functions to be called
var handler =
{ get(target, fnName, receiver) {
if (fnName in target){ //eg. .double : get the function and push it
pipe.push(target[fnName]);
return receiver;} //receiver is our Proxy object: to(3)
if (fnName == "get")
return pipe.reduce(function (val, fn) { return fn(val) }, value);
throw Error('Method: '+ fnName +' not yet supported');
}, set(target, fnName, fn, receiver) {
target[fnName] = fn;} //dynamic declaration of functions
};
return new Proxy(functionsProvider, handler);}}());
DSL with Proxies- implementation // ==== to(3).double.pow.get === return function toImplementation(value) {
var pipe = []; //stores functions to be called
var handler =
{ get(target, fnName, receiver) {
if (fnName in target){ //eg. .double : get the function and push it
pipe.push(target[fnName]);
return receiver;} //receiver is our Proxy object: to(3)
if (fnName == "get")
return pipe.reduce(function (val, fn) { return fn(val) }, value);
throw Error('Method: '+ fnName +' not yet supported');
}, set(target, fnName, fn, receiver) {
target[fnName] = fn;} //dynamic declaration of functions
};
return new Proxy(functionsProvider, handler);}}());
DSL with Proxies- implementation // ==== to(3).double.pow.get === return function toImplementation(value) {
var pipe = []; //stores functions to be called
var handler =
{ get(target, fnName, receiver) {
if (fnName in target){ //eg. .double : get the function and push it
pipe.push(target[fnName]);
return receiver;} //receiver is our Proxy object: to(3)
if (fnName == "get")
return pipe.reduce(function (val, fn) { return fn(val) }, value);
throw Error('Method: '+ fnName +' not yet supported');
}, set(target, fnName, fn, receiver) {
target[fnName] = fn;} //dynamic declaration of functions
};
return new Proxy(functionsProvider, handler);}}());
DSL with Proxies- implementation // ==== to(3).double.pow.get === return function toImplementation(value) {
var pipe = []; //stores functions to be called
var handler =
{ get(target, fnName, receiver) {
if (fnName in target){ //eg. .double : get the function and push it
pipe.push(target[fnName]);
return receiver;} //receiver is our Proxy object: to(3)
if (fnName == "get")
return pipe.reduce(function (val, fn) { return fn(val) }, value);
throw Error('Method: '+ fnName +' not yet supported');
}, set(target, fnName, fn, receiver) {
target[fnName] = fn;} //dynamic declaration of functions
};
return new Proxy(functionsProvider, handler);}}());
const barker = (state) => ({ //factory function barker
bark: () => console.log('woof, woof ' + state.name)
})
const angryHuman = (name) => { //factory function angryHuman
let state = {name}; //state object stays in the closure
return Object.assign( //assign to {} all own properties of barker(state)
{},
barker(state),
talker(state)
)
}
var angryJavi = angryHuman('javi')
angryJavi.bark() //woof, woof javi
Composition with Object.assign
const barker = (state) => ({ //factory function barker
bark: () => console.log('woof, woof ' + state.name)
})
const angryHuman = (name) => { //factory function angryHuman
let state = {name}; //state object stays in the closure
return Object.assign( //assign to {} all own properties of barker(state)
{},
barker(state),
talker(state)
)
}
var angryJavi = angryHuman('javi')
angryJavi.bark() //woof, woof javi
Composition with Object.assign
const barker = (state) => ({ //factory function barker
bark: () => console.log('woof, woof ' + state.name)
})
const angryHuman = (name) => { //factory function angryHuman
let state = {name}; //state object stays in the closure
return Object.assign( //assign to {} all own properties of barker(state)
{},
barker(state),
talker(state)
)
}
var angryJavi = angryHuman('javi')
angryJavi.bark() //woof, woof javi
Composition with Object.assign
references● Alex Rauschmayer on Proxies: http://www.2ality.com/2014/12/es6-proxies.html
● About quines: http://c2.com/cgi/wiki?QuineProgram
● Kiczales on metaprogramming and AOP: http://www.drdobbs.com/its-not-metaprogramming/184415220
● Brendan Eich. Proxies are awesome: http://www.slideshare.net/BrendanEich/metaprog-5303821
● eval() isn’t evil, just misunderstood: http://www.nczonline.net/blog/2013/06/25/eval-isnt-evil-
just-misunderstood/
● On DI: http://krasimirtsonev.com/blog/article/Dependency-injection-in-JavaScript
● Express middlewares: http://expressjs.com/guide/using-middleware.html
● Proxies by Daniel Zautner: http://www.dzautner.com/meta-programming-javascript-using-proxies/
Media● Storm by Kelly Delay: https://flic.kr/p/seaiyf
● The complete explorer: https://www.flickr.com/photos/nlscotland/
● Record Player by Andressa Rodrigues: http://pixabay.com/en/users/AndressaRodrigues-40306/
● Wall by Nicole Köhler: http://pixabay.com/en/users/Nikiko-268709/
● Remote by Solart: https://pixabay.com/en/users/solart-621401/
● Rocket launch by Space-X: https://pixabay.com/en/users/SpaceX-Imagery-885857/
● Coffee by Skeeze: https://pixabay.com/en/users/skeeze-272447/
● Sleeping Monkey by Mhy: https://pixabay.com/en/users/Mhy-333962/
● Funny Monkey by WikiImages: https://pixabay.com/en/users/WikiImages-1897
● Lemur by ddouk: https://pixabay.com/en/users/ddouk-607002/
● Fire in the sky by NASA: https://flic.kr/p/pznCk1
●
function sayHi(name){ console.log('Hi '+name+'!') }// we define a very interesting function
sayHi = functionSpy(sayHi);// now we Spy on sayHi function.
console.log('calledOnce?', sayHi.once); // false. Note that ‘once’ looks like a property!!
sayHi('Gregor'); // calling our Function!!
console.log('calledOnce?', sayHi.once); // true
function functionSpy(func){
var proxyFunc = function () { //intercept and count calls to func
proxyFunc._callCount += 1;
return func.apply(null, arguments);
};
Object.defineProperty(proxyFunc, "_callCount", {value: 0, writable: true});
Object.defineProperty(proxyFunc, "once", {get: function(){return this._callCount==1});
return proxyFunc;
}
Test Spy
function constructor vs eval
function functionCreate(aParam) { //Func consctructor cannot access to the closure
var funcAccessToClosure = Function('a', 'b', 'return a + b + aParam');
return funcAccessToClosure(1, 2);
}
functionCreate(3); //ReferenceError: aParam is not defined
function functionInEval(aParam) {//has access to the closure
eval("function funcAccessToClosure(a, b){return a + b + aParam}");
return funcAccessToClosure(1, 2);
}
functionInEval(3); // 6
var aParam = 62; //Now, define aParam.
functionCreate(3); // 65
functionInEval(3); // 6
DI container
● Function reflection (parameters) eg: Dependency Injection
var Injector = {dependencies: {},
add : function(qualifier, obj){
this.dependencies[qualifier] = obj;},
get : function(func){
var obj = Object.create(func.prototype);
func.apply(obj, this.resolveDependencies(func));
return obj;},
resolveDependencies : function(func) {
var args = this.getParameters(func);
var dependencies = [];
for ( var i = 0; i < args.length; i++) {
dependencies.push(this.dependencies[args[i]]);}
return dependencies;},
getParameters : function(func) {//This regex is from require.js
var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m;
var args = func.toString().match(FN_ARGS)[1].split(',');
return args;}};
var aFancyLogger = {
log: function(log){console.log(Date().toString()+" => "+ log);}
};
var ItemController = function(logger){
this.logger = logger;
this.doSomething = function(item){this.logger.log("Item["+item.id+"] handled!");};
};
Injector.add("logger", aFancyLogger); //register logger into DI container
var itemController = Injector.get(ItemController); //get Item controller from DI
itemController.doSomething({id : 5});
proxy sample: DRY REST Client// DRY REST client
function prepareGetter(resource) {
return function resourceGetter(id) {
console.log('HTTP GET /server/'+resource+( id ? '/'+id : ''));
return 200;
}
}
let proto = new Proxy({}, {
get(target, name, receiver) {
if(name.startsWith('get')) {
return prepareGetter(name.slice(3).toLowerCase());}
return target[name];
}
});
let myRestClient = Object.create(proto); //Prototype is a Proxy
myRestClient.allo = 7;
myRestClient.getClient('kent.beck'); //200 "HTTP GET /server/client/kent.beck"
myRestClient.allo; // 7;
DSL with Proxiesvar to = (function closure() {
var functionsProvider = {
double: function (n) { return n*2 },
pow: function (n) { return n*n }
};
return function toImplementation(value) {
var pipe = [];
var handler =
{
get(target, fnName, receiver) {
if (fnName == "get")
return pipe.reduce(function (val, fn) { return fn(val) }, value);
if (fnName in target) {
pipe.push(target[fnName]);
return receiver;}
throw Error('Method: '+ fnName +' not yet supported');
},
set(target, fnName, fn, receiver) {
target[fnName] = fn;} //dynamic declaration of functions
};
return new Proxy(functionsProvider, handler);}}());
console.log('to(3).double.pow.get::',to(3).double.pow.get); // 36
console.log('to(2).triple::', to(2).triple.get); //Error: Method: triple not yet supported
to().triple = function(n) {return n*3};
console.log('to(2).triple::',to(2).triple.get);
Composition with Object.assignconst barker = (state) => ({ //factory function barker
bark: () => console.log('woof, woof ' + state.name)
})
const angryHuman = (name) => { //factory function angryHuman
let state = {name}; //state object stays in the closure
return Object.assign( //assign to {} all own properties of barker(state)
{},
barker(state)
)
}
var angryJavi = angryHuman('javi')
angryJavi.bark() //woof, woof javi
proposal
Description
ES6 delivers some exciting metaprogramming capabilities with its new Proxies feature.
Metaprogramming is powerful, but remember: "With great power comes great responsibility". In the
talk we will shortly revisit Javascript metaprogramming and explain ES6 Proxies with code
examples.
Session type: 40-minute session
Topics: none
Abstract
During the talk we will explain different metaprogramming concepts and techniques with code
samples related to a very simple testing library.
First, we will discuss about what metaprogramming is, what metaprogramming is useful for and a
very light overview of metaprogramming in other programming languages.
Current capacities from Javascript up to ES5 will be revisited with some code examples.
Finally, ES6 and proxies will be covered.