Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

ES6 metaprogramming unleashed

1,458 views

Published on

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.

Published in: Software
  • Be the first to comment

ES6 metaprogramming unleashed

  1. 1. ES6 metaprogramming unleashed
  2. 2. are we ready? Metaprogramming is powerful and fun, but remember: "With great power comes great responsibility"
  3. 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
  4. 4. metaprogramming “The key property of metaprograms is that manipulate other programs or program representations.” - Gregor Kiczales
  5. 5. metaprogramming levels Meta level: program manipulating other program Base level: program manipulated
  6. 6. metaprogramming samples Compiler/transpilers: gcc, coffeescript… Macro languages (eg. C preprocessor) Using "eval" to execute a string as code Database scaffolding/ORM: mongoose, … IDEs: Webstorm, …
  7. 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. 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 });
  9. 9. Self-modification Self-modification: change program structure. function renameProperty(obj, oldName, newName) { obj[newName] = obj[oldName]; delete obj[oldName]; } renameProperty(obj, 'hello', 'greet');
  10. 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. 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. 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. 13. JS metaprogramming up to ES5 THE GOOD: object metaprogramming API THE UGLY: function metaprogramming THE BAD: eval
  14. 14. The Good: Object metapgrming API ● modify property access: ○ getters & setters ○ property descriptors ● Object mutability: preventExtensions, seal, freeze
  15. 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. 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. 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. 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. 19. The Ugly: func metaprogramming ● Function constructor ● Function reflection: ○ Function.prototype.length ○ Ugly hacks
  20. 20. var remainder = new Function('a', 'b', 'return a % b;'); remainder(5, 2); // 1 function constructor Seems similar to eval but…
  21. 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. 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. 23. function reflection - length Function.length returns the number of parameters of a function. Usage example: Express checking middlewares signature
  24. 24. function parameters reflection We want to get informaton about function parameters. Parameters belong to function signature. Arguments are passed to a function call.
  25. 25. function parameters reflection Is it a bad joke? Function.toString() + RegExp to the rescue! How do we do that in JS?
  26. 26. DI container implementation Defined in ES5 and ES2015 specs.
  27. 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:
  28. 28. Eyes still opened? Coffee must be working!
  29. 29. ES6 and Proxy The Proxy can define custom behavior for fundamental operations. property lookup, assignment, enumeration, function invocation
  30. 30. Proxy concepts handler: interceptor. traps per operation. proxy & handler target A Proxy wraps a target object. target: proxied object.
  31. 31. 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
  32. 32. 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]
  33. 33. Proxy usages Why do we need proxies? virtual objects: persistent or remote objects emulate the dreaded "host objects" do fancy things such as DSLs
  34. 34. proxy sample: DRY REST Client // REST client let myRestClient = { getClient: function(id) { console.log('HTTP GET /server/client/'( id ? '/'+id : '')); return 200; }, getBill: function(id) { console.log('HTTP GET /server/bill/'( id ? '/'+id : '')); return 200; }, // (...) DO YOU SEE THE PATTERN? } myRestClient.allo = 7; myRestClient.getClient('kent.beck'); //200 "HTTP GET /server/client/kent.beck" myRestClient.allo; // 7;
  35. 35. proxy sample: 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;
  36. 36. DSL with Proxies to(3).double.pow.get // 36
  37. 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. 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. 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. 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. 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. 42. That’s all folks! No animals were harmed in the preparation of this presentation.
  43. 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. 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/
  45. 45. complete code examples
  46. 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. 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. 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);

×