Closures are a powerful and important feature of Javascript. Before trying to understand the syntax of how to create one it's important to understand just why they are an important feature of the language. Firstly closures are a way of creating objects. But, so what? Javascript already provides two ways to do this: Object literals and Constructor functions.
So what's wrong with these mechanisms? Well they do not provide any sort of encapsulation. Get a handle to an object created via the literal object approach or the constructor function approach and you can pretty much can change anything in the object.
Watch this poor literal object's encapsulation dreams smash...
Closures provide a mechanism for encapsulating objects. You want to encapsulate Tony's name (and every other person's name) do this:
Currying
Currying is the process of reducing the number of arguments passed to a function by setting some argument(s) to predefined values. Consider this function...
And then defined a variable to be this new function...
As can be seen, the number of arguments has been reduced from 2 to 1. And this has been enabled because the argument that has been eliminated has been set to a fixed value, in this case its value is 1.
Currying works by creating a closure that holds the original function and the arguments to curry. A generic solution to currying is to add a "curry" function to every function in our code by augmenting function's prototype to include a curry function.
Watch this poor literal object's encapsulation dreams smash...
myObject = { myProperty:"I wish I was encapsulated" } console.log(myObject.myProperty); //outputs I wish I was encapsulated myObject.myProperty = "sorry mate you're note"; console.log(myObject.myProperty); //outputs sorry mate you're noteAs for the Constructor function...
var Person = function(name) { this.name = name; this.speak = function () { return "I am " + this.name; } } var tony = new Person("Tony"); console.log(tony.speak()); // outputs I am Tony // Tony's encapsulation dreams are going to get smashed. tony.name = "Fatso"; console.log(tony.speak()); // outputs I am fatsoThis is awful. Put yourselves in Tony's shoes and think about how he feels. This lack of encapsulation causes problems. Some programmers used the convention of an underscore before a property (as in _name) to try to indicate they wished they could make a property private but they couldn't. The underscore was saying: "please, please don't touch me". But who wants to engineer logical systems around emotional pleas?
Closures provide a mechanism for encapsulating objects. You want to encapsulate Tony's name (and every other person's name) do this:
var person = function(name) { console.log(">> setting name to " + this.name); return { getName: function(){ return name; } } } var tony = person("Tony"); // outputs setting name to Tony console.log(tony.getName()); // outputs Tony tony.name = "Fatso" // it won't change the name in the closure console.log(tony.getName()); // still outputs TonyWOW! at last some encapsulation. Let me try to explain... The outer function returns an Object literal. This lives longer than the outer function. The outer function effectively ends as soon as person("tony") is finished. The object literal lives longer because it is returned. Properties and methods in the object literal can access variables in the outer function. Just like the way any inner function can access variables in the outer function. Now, when the object literal is returned it "closes" over the values of the variables in the outer function it can access. Effectively, getting keeping a copy of them. It would be the exact same for an inner function if it was returned. What is returned is called the closure. The outside world cannot directly access what the closure closes over. It can only access what the closure itself advertises to the outside world. In this case, that is just one function getName(). This all means we can make encapsulated person objects.
Currying
Currying is the process of reducing the number of arguments passed to a function by setting some argument(s) to predefined values. Consider this function...
function outputNumbers(begin, end) { var i; for (i = begin; i <= end; i++) { print(i); } } outputNumbers(0, 5); // outputs 0, 1, 2, 3, 4, 5 outputNumbers(1, 5); // outputs 1, 2, 3, 4, 5Now suppose we want a similar function with a fixed "begin" value. Let's say the "begin" value was always 1. We could do:
function outputNumbersFixedStart(start) { return function(end) { return outputNumbers(start, end); } }
And then defined a variable to be this new function...
var outputFromOne = outputNumbersFixedStart(1); outputFromOne(3); 1, 2, 3 outputFromOne(5); 1, 2, 3, 4, 5
As can be seen, the number of arguments has been reduced from 2 to 1. And this has been enabled because the argument that has been eliminated has been set to a fixed value, in this case its value is 1.
Currying works by creating a closure that holds the original function and the arguments to curry. A generic solution to currying is to add a "curry" function to every function in our code by augmenting function's prototype to include a curry function.
Function.prototype.curry = function() { if (arguments.length<1) { return this; //nothing to curry with - return function } var that = this; var slice = Array.prototype.slice; var args = slice.apply(arguments); return function() { var innerFunctionSlice = slice.apply(arguments); return that.apply(null, args.concat(slice.apply(arguments))); } }This curry function can be applied to any function.
var outputFromZeroCurried = outputNumbers.curry(0); var outputFromOneCurried = outputNumbers.curry(1); outputFromZeroCurried(3); // outputs 0, 1, 2, 3 outputFromOneCurried(5); // outputs 1, 2, 3, 4, 5Note: One thing to bear in mind in the curry prototype function is that the value of arguments in the outer function is different to the value of arguments in inner function. When outputNumbers.curry(0) is invoked, arguments in the outer function is {0}. When outputFromZeroCurried(3) is invoked, arguments for inner function is {3}, but arguments for the outer function is still {0}. Essentially, 'arguments' in the outer function refers to arguments passed to the outer function; 'arguments' for the inner function refers to arguments for the inner fuction.
This smells like a Curry! |
Marvelous! Why didn't I think of creating a curry prototype on function? Partly, I'll admit because this generic solution would have been a little beyond me!
ReplyDeleteI'm writing code that uses lots of currying but have been currying the functions statically function (x1,... xn) { return function (xm...xn) } each time I need one rather than this general-purpose solution which has the additional benefit of being more explicit in its behavior in the code.
Thanks.
Maybe this will make me look stupid...but oh well...i understood up to half of that currying part...once you got into the big curry function, it was too hard for me to understand...i mean, i can step through it but it seems like overkill...I like the first simpler curry function.
ReplyDelete