Functional programming using underscorejs
Transcript of Functional programming using underscorejs
![Page 1: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/1.jpg)
Functional Programmingusing Underscore.js
othree @ OSDC 2013
![Page 2: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/2.jpg)
me
• @othree
• https://blog.othree.net
![Page 3: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/3.jpg)
me
• PhD Candidate
• Front End Engineer
• On the way learning good from functional programming languages
![Page 4: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/4.jpg)
Function ofFunctional Language
• Pure function
• First class citizen
• Higher order function
• ...
![Page 5: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/5.jpg)
Pure Function
• Side effect free
• Same input, same output
• ex: trigonometric functions
![Page 6: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/6.jpg)
First Class Citizen
• Function like variable
• ex: function expression
var f = function () { /*...*/ };
![Page 7: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/7.jpg)
Higher Order Function
• Function takes or return functions
• ex: event listener
![Page 8: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/8.jpg)
http://www.flickr.com/photos/78428166@N00/6036104277/
![Page 9: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/9.jpg)
Take a LookExamples from ‘Pure, functional JavaScript’
http://cjohansen.no/talks/2012/sdc-functional/
![Page 10: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/10.jpg)
[ { name: 'Gates', gender: 'M', org: 'M$' }, { name: 'Peter' gender: 'M', org: 'Hㄒㄈ' }]
![Page 11: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/11.jpg)
"Mentioned by Gates, Peter, Jobs"
![Page 12: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/12.jpg)
var str = "Mentioned by ";
for (var i = 0, l = tweeps.length; i < l; ++i) { str += tweeps[i].name; if (i < tweeps.length - 1) { str += ", "; }}
![Page 13: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/13.jpg)
[ { name: 'Gates', gender: 'M', org: 'M$' }, { name: 'Peter' gender: 'M', org: 'Hㄒㄈ' }]
![Page 14: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/14.jpg)
[ 'Gates', 'Peter']
![Page 15: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/15.jpg)
var names = [];
for (var i = 0, l = tweeps.length; i < l; ++i) { names.push(tweeps[i].name);}
![Page 16: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/16.jpg)
var str = "Mentioned by " + names.join(", ");
![Page 17: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/17.jpg)
var names = tweeps.map(function (tweep) { return tweep.name;});
![Page 18: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/18.jpg)
var names = tweeps.map(function (t) { return t.name; });
![Page 19: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/19.jpg)
var str = "Mentioned by " + tweeps.map(function (t) { return t.name;}).join(", ");
![Page 20: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/20.jpg)
function prop(name) { return function (object) { return object[name]; };}
![Page 21: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/21.jpg)
var str = "Mentioned by " + tweeps.map(prop("name")).join(", ");
![Page 22: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/22.jpg)
Case 2
![Page 23: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/23.jpg)
[ { getSummary: function () { return { text: 'Summaries', html: '<p>Summaries</p>' }; } }, { getSummary: function () { return {text: 'Summaried'}; } }, ...]
![Page 24: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/24.jpg)
<div> <p>Summaries</p> <p>Summaried</p> <p>Summary</p></div>
![Page 25: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/25.jpg)
buildSummary: function () { var div = document.createElement("div"), p;
for (var i = 0, l = this.components.length; i < l; ++i) { p = document.createElement("p"); p.innerHTML = this.components[i].getSummary().text; div.appendChild(p); }
return div;}
![Page 26: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/26.jpg)
DOM functions
![Page 27: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/27.jpg)
var ul = cull.dom.el("ul");ul.nodeType === 1 // true
https://github.com/culljs/dome/
![Page 28: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/28.jpg)
var ul = cull.dom.el("ul", { className: "bands" });
//var li = cull.dom.el("li", "Execration");var ul = cull.dom.el("ul", { className: "bands" }, li);
![Page 29: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/29.jpg)
var ul = cull.partial(cull.dom.el, "ul");var li = cull.partial(cull.dom.el, "li");
![Page 30: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/30.jpg)
["a", "br", "code", "div", ...].forEach(function (tagName) { cull.dom.el[tagName] = cull.partial(cull.dom.el, tagName);});
//["a", "br", "code", "div", ...].forEach(function (tagName) { root[tagName] = cull.partial(cull.dom.el, tagName);});
![Page 31: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/31.jpg)
http://www.flickr.com/photos/jackhynes/519904699/
![Page 32: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/32.jpg)
buildSummary: function () { return div(this.components.map(function (component) { return p(component.getSummary().text); }));}
![Page 33: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/33.jpg)
buildSummary: function () { return div(this.components.map(function (component) { return p(component.getSummary().text); }));} 1
![Page 34: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/34.jpg)
buildSummary: function () { return div(this.components.map(function (component) { return p(component.getSummary().text); }));} 1 2
![Page 35: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/35.jpg)
buildSummary: function () { return div(this.components.map(function (component) { return p(component.getSummary().text); }));} 1 23
![Page 36: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/36.jpg)
buildSummary: function () { return div(this.components. map(function (component) { return component.getSummary(); }).map(function (summary) { return summary.text; }).map(function (text) { return p(text); }));}
![Page 37: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/37.jpg)
function func(name) { return function (object) { return object[name](); };}
![Page 38: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/38.jpg)
buildSummary: function () { return div(this.components. map(func("getSummary")). map(function (summary) { return summary.text; }).map(function (text) { return p(text); }));}
![Page 39: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/39.jpg)
buildSummary: function () { return div(this.components. map(func("getSummary")). map(prop("text")). map(function (text) { return p(text); }));}
![Page 40: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/40.jpg)
buildSummary: function () { return div(this.components. map(func("getSummary")). map(prop("text")). map(p));}
![Page 41: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/41.jpg)
var summarize = compose([p, prop("text"), func("getSummary")]);
![Page 42: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/42.jpg)
var callGetSummary = func("getSummary");var getText = prop("text");var summarize = compose([p, getText, callGetSummary]);
// summarize(obj);// => callGetSummary(obj)// => getText(callGetSummary(obj))// => p(getText(callGetSummary(obj)))
![Page 43: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/43.jpg)
buildSummary: function () { var summarize = compose([p, prop("text"), func("getSummary")]); return div(this.components.map(summarize));}
![Page 44: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/44.jpg)
var summarize = compose([p, prop("text"), func("getSummary")]);
// ...
buildSummary: function () { return div(this.components.map(summarize));}
![Page 45: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/45.jpg)
http://www.flickr.com/photos/guerson/5630633727/
![Page 46: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/46.jpg)
Functional Programming in JavaScript
![Page 47: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/47.jpg)
Native
• forEach
• map/reduce
• filter
![Page 48: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/48.jpg)
Functional JavaScript
• by Oliver Steele at 2007
• First functional JavaScript Library I know
![Page 49: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/49.jpg)
Underscore.js
• by Jeremy Ashkenas from DocumentCloud
• “Underscore is a utility-belt library for JavaScript that provides a lot of the functional programming support”
![Page 50: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/50.jpg)
Lo-Dash
• Will talk later
![Page 51: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/51.jpg)
cull.js
• by Christian Johansen and Magnar Sveen
• “Cull is a toolbelt for writing functional javascript.”
• Used in the examples above
https://github.com/culljs/culljs
![Page 52: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/52.jpg)
LiveScript & prelude.ls
• by George Zahariev
• A new compile to JavaScript language fork from Coco
• Stay in this room until tomorrow, Mindos have a talk about LiveScript
![Page 53: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/53.jpg)
GHCJS
• by Hamish Mackenzie, Victor Nazarov, Luite Stegeman
• Haskell to JavaScript compiler
![Page 54: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/54.jpg)
Underscore.js
• compose
• map/reduce
• filter
• pluck
![Page 55: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/55.jpg)
var str = "Mentioned by " + tweeps.map(prop("name")).join(", ");
![Page 56: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/56.jpg)
var str = "Mentioned by " + _.reduce( _.map(tweeps, function (t) { return t.name; }), function (memo, name, i) { return (i == 0) ? name : memo + ', ' + name; }, '');
![Page 57: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/57.jpg)
var str = "Mentioned by " + _(tweeps) .chain() .map(function (t) { return t.name; }) .reduce(function (memo, name, i) { return (i == 0) ? name : memo + ', ' + name; }, '') .value();
![Page 58: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/58.jpg)
var str = "Mentioned by " + _(tweeps) .map(function (t) { return t.name; }) .join(', ');
![Page 59: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/59.jpg)
var str = "Mentioned by " + _(tweeps).pluck('name').join(', ');
![Page 60: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/60.jpg)
Still Not Enough
• curry, partial
• prop, func from above example
![Page 61: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/61.jpg)
Lo-Dash
![Page 62: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/62.jpg)
Functional Programmingusing Underscore.js
othree @ OSDC 2013.1
![Page 63: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/63.jpg)
Functional Programmingusing Underscore.jsLo-Dash
othree @ OSDC 2013.1
![Page 64: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/64.jpg)
-
![Page 65: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/65.jpg)
_
![Page 66: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/66.jpg)
_-
![Page 67: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/67.jpg)
_-
![Page 68: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/68.jpg)
What is Lo-Dash
• Underscore.js fork by John-David Dalton, Kit Cambridge, and Mathias Bynens
![Page 69: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/69.jpg)
Difference
• Better performance
• Robust result
• Larger file size
• AMD supports
• Auto chain
• More power: cloneDeep, partial, result...
![Page 70: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/70.jpg)
_.partial
var greet = function(greeting, name) { return greeting + ' ' + name; };var hi = _.partial(greet, 'hi');hi('moe');// ! 'hi moe'
http://lodash.com/docs#partial
![Page 71: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/71.jpg)
_.result
var object = { 'cheese': 'crumpets', 'stuff': function () { return 'nonsense'; }};
_.result(object, 'cheese');// ! 'crumpets'
_.result(object, 'stuff');// ! 'nonsense'
http://lodash.com/docs#result
![Page 72: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/72.jpg)
With Lo-Dash
![Page 73: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/73.jpg)
var summarize = compose([p, prop("text"), func("getSummary")]);
// ...
buildSummary: function () { return div(map(summarize), this.components);}
![Page 74: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/74.jpg)
var summarize = _.compose( p, _.partialRight(_.result, 'name'), _.partialRight(_.result, 'getSummary') );
// ...
buildSummary: function () { return div(_.map(this.components, summarize));}
![Page 75: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/75.jpg)
Performance?
![Page 76: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/76.jpg)
Bad..
![Page 77: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/77.jpg)
http://jsperf.com/for-vs-foreach/71
![Page 78: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/78.jpg)
http://jsperf.com/for-vs-foreach/71
![Page 79: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/79.jpg)
• Take benefits from functional programming
• Not change everything to functional
• Library helps, ex: lo-dash
![Page 80: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/80.jpg)
ReferencesFurther Readings
![Page 81: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/81.jpg)
http://interglacial.com/hoj/hoj.html
![Page 82: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/82.jpg)
http://cjohansen.no/talks/2012/sdc-functional/
![Page 83: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/83.jpg)
http://kitcambridge.be/blog/say-hello-to-lo-dash/
![Page 84: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/84.jpg)
http://www.slideshare.net/ihower/fp-osdc2012v2
![Page 85: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/85.jpg)
http://shop.oreilly.com/product/9781593272821.do
![Page 86: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/86.jpg)
http://shop.oreilly.com/product/0636920028857.do
![Page 87: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/87.jpg)
http://shop.oreilly.com/product/0636920028857.do
Not
Yet
Rel
ease
d
![Page 88: Functional programming using underscorejs](https://reader031.fdocuments.in/reader031/viewer/2022020116/558c8c5ed8b42a6c678b471f/html5/thumbnails/88.jpg)
Questions?
http://www.flickr.com/photos/roman/5610736/