ES6 Metaprogramming presentation at MediterraneaJS on June 22th, 2015.
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.
3. about me
Javier Arias Losada, senior software engineer at Telefonica.
Currently working at EyeOS, helping to create an VDI (Virtual
Desktop Infrastructure) platform.
@javier_arilos
http://about.me/javier.arilos
7. reflective metaprogramming
A program that modifies itself -
Reflective Metaprogramming in JS-
This is the subject of the talk!
MMM… very
interesting … but
when are we
going to JS?
8. Introspection
Introspection: read the structure of a program.
Object.keys()
var obj = { //Base level
name: 'Santa',
hello: function() {
console.log('Hello', this.name,'!');
}
};
Object.keys(obj).forEach(function(prop) {
console.log(prop); //Meta level
});
10. Intercession
Intercession: redefine semantics of some language operations.
Object.defineProperty(obj, "roProp", {
value: 101,
writable: false,
enumerable: true});
obj.roProp = 102
obj.roProp //101
11. ‘Cheating’ quine:
(function a(){console.log('('+a+')()')})()
‘Cheating’ quine:
(function a(){console.log('('+a+')()')})()
A real one:
_='"'+";document.write('_=',_[17],_[0],_[17],'+',_,_)";document.write('_=',_[17],_
[0],_[17],'+',_,_)
metaprogramming fun: Quines
Quine: a program that prints a copy of its own
source code (no input allowed)
12. Caffeine Facts:
A cup of coffee works just 10
minutes after you drink it.
It takes 45 minutes for 99% of
caffeine to be absorbed.
Please… awake people
around you!!
13. JS metaprogramming up to ES5
THE GOOD:
object metaprogramming API
THE UGLY:
function metaprogramming
THE BAD:
eval
15. Sample: 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
16. // we define a very interesting function
function sayHi(name){ console.log('Hi '+name+'!') }
// now we Spy on sayHi function.
sayHi = functionSpy(sayHi);
console.log('calledOnce?', sayHi.once); // false. Note that ‘once’ looks like a property!!
sayHi('Gregor'); // calling our Function!!
console.log('calledOnce?', sayHi.once); // true
Sample: Test Spy
17. accessor properties sample - Spy
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;
}
18. 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
19. The Ugly: func metaprogramming
● Function constructor
● Function reflection:
○ Function.prototype.length
○ Ugly hacks
20. var remainder = new Function('a', 'b', 'return a % b;');
remainder(5, 2); // 1
function constructor
Seems similar to eval but…
21. 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
22. 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
23. function reflection - length
Function.length returns the number of parameters
of a function.
Usage example: Express checking middlewares signature
24. function parameters reflection
We want to get informaton about function
parameters.
Parameters belong to function signature.
Arguments are passed to a function call.
27. getParameters : function(func) { //The regex is from Angular
var FN_PARAMS = /^functions*[^(]*(s*([^)]*))/m;
var funcParams = func.toString().match(FN_PARAMS)[1].split(',');
return funcParams;
}
fucntion parameters reflection
Some libraries with Dependency Injection, such
as Angular.js use this technique:
37. 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);
}
}());
38. 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);}}());
39. 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);}}());
40. 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);}}());
41. DSL with Proxies - new methods
to(2).triple.get; //Error: Method: triple not yet supported
to().triple = function(n) {return n*3}; //Add new method: triple
to(2).triple.get; // 6
42. That’s all folks!
No animals were harmed in the preparation of
this presentation.
43. 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/
44. 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/
46. 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
47. 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 = /^functions*[^(]*(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});
48. DSL with Proxies
var 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);
Hinweis der Redaktion
Programs have a domain.
Shipping system domain are customers, orders…
Metaprograms domain is programs
people need to extend javascript, not only with c++ with a lot of security issues, but with javascript itself in order to continue to extend the language and the libraries in a way that they look as built-in objects. ant that can virtualize/substitute builtin objects.
that's very important when you implement libraries.
we want to rebuild the world Smalltalk had, we want to get to the very low native code (C++) nirvana. the proxies here are brilliant, otherwise I do not know how we will get there.