Currying
Currying sounds like some secret sauce, however the name is derived from Haskell Brooks Curry, the American Mathematician (1900 – 1982).
This technique involves predefining a function, with a partial list of arguments using a sort of “proxy function”. Whenever you have to call the same function several times, using some of the arguments repeatedly, then Currying can be used to avoid repeated calls to the same function. The following snippet shows a general purpose Currying function.
Function.prototype.partial = function(){ var fn = this, args = Array.prototype.slice.call(arguments); return function() { var arg = 0; for (var i = 0; i < args.length && arg < arguments.length; i++) { if (args[i] === undefined) { args[i] = arguments[arg++]; } } return fn.apply(this, args); }; }; String.prototype.csv = String.prototype.split.partial(/,\s*/); var results = ("Dock, Cruise, Pier").csv(); if(results[0] == "Dock" && results[1] == "Cruise" && results[2] == "Pier"){ alert("The string was split as expected"); }
Memoization
Memoization is a technique that is often used to improve the performance of JavaScript functions, by storing precomputed values in a function’s property or cache. In this example, isPrime()
is an immediate function that gets memoize()
method applied to it. This method wraps the memoized()
method around the original isPrime()
function through a closure. So we end up with a chain of functions: isPrime()
=> memoize()
=> memoized()
.
Function.prototype.memoized = function(key){ this._values = this._values || {}; return this._values[key] !== undefined ? this._values[key] : this._values[key] = this.apply(this, arguments); }; Function.prototype.memoize = function(){ var fn = this; return function(){ return fn.memoized.apply( fn, arguments ); }; }; var isPrime = (function(num){ var prime = num != 1; for (var i = 2; i < num; i++){ if (num % i == 0){ prime = false; break; } } return prime; }).memoize(); if (isPrime(23)){ alert ("23 is a prime number"); }
Function wrapping
The example below shows how the Prototype JavaScript library uses function wrapping to resolve a cross-browser issue with Opera’s implementation of accessing HTML title attributes. At first the code looks like a riddle wrapped in mystery, inside an enigma. Several things are happening in just a few lines of code. The wrap()
function takes three arguments:
- the object whose method is being wrapped,
- then the method to be wrapped,
- and finally the wrapping function.
Next we use the same technique used for memoization in the previous example to wrap the wrapper
function around object[method]
lines 2 – 6. In this case using apply()
with object[]
as context, and creating an arguments list by concatinating the original arguments with the method being wrapped. Effectively, object[method] now executes the wrapper
function (via apply()
). Note that the wrapper
function has not being explicitly defined yet, it’s created when you use the wrap
function in line 19. The apply()
method is used to give the wrapper function its context and arguments list using the bind()
method created in line 10. If you find this technique hard to digest, you are not alone. It may take a while to wrap your head around it (pun intended).
function wrap(object, method, wrapper) { var fn = object[method]; return object[method] = funtion(){ return wrapper.apply(this, [fn.bind(this)].concat( Arry.prototype.slice.call(arguments))); }; } Function.prototype.bind = function(){ var fn = this, args = Array.prototype.slice.call(arguments), object = args.shift(); return function(){ return fn.apply(object, args.concat(Array.prototype.slice.call(arguments))); } } if (Prototype.Browser.Opera) { wrap(Element.Methods, "readAttribute", function(original, elem, attr){ return attr == "title" ? elem.title : original(elem, attr); }); }
Leave a Reply