Different implementations of higher order functions. Refering mostly to:
- Standard ECMA-626 Language Specification
- Underscore
- Lodash
- [Underbar]
- Ramda
- functional
- lazy
- wu
- bacon
- sloth
"Iteratee: a composable abstraction for processing sequentially presented chunks of input data in a purely functional fashion".
When refering to a function that is passed into another function, I will say "callback"
This is purely for educational purposes - just to have a look under the hood at how some libraries are writing implementations of native JavaScript higher-order functions.
Iterate over an input list, calling a provided function, fn for each element in the list.
Syntax
Array.forEach( callback( value, key, collection ) [this] )function forEach( fn, list ) {
var len = list.length;
var idx = 0;
while ( idx < len) {
fn( list[idx] );
idx += 1;
}
return list;
}-
arguments: takes a function,
fnand an array,listas arguments -
declares variables -
len, the list of the array passed in -idx, an index used to traverse the array -
while loop: while the index,
idx, is less thanlenthe length of thelist,- call the function passed as
fnargument- function
fntakes two arguments,listandidx - call
fnon thelistitem at positionidx - increment
idxby 1. - when
idxis larger thanlen, break out of the loop.
- function
- call the function passed as
-
return
list
_.forEach = function( obj, iteratee, context ) {
iteratee = optimizeCb( iteratee, context );
var i, length;
if ( isArrayLike( obj ) ) {
for (i = 0, length = obj.length; i < length; i++) {
iteratee(obj[i], i, obj);
}
} else {
var keys = _.keys(obj);
for (i = 0, length = keys.length; i < length; i++) {
iteratee(obj[keys[i]], keys[i], obj);
}
}
return obj;
};lodash uses an internal function called iterator() that takes three arguments, value, key, and collection. These are the same as the parameters the callback on JavaScripts Array.prototype.forEach (MDN calls it currentValue, index, and array). Unlike the native forEach, this handles non-array objects.
_.each = function(collection, iterator) {
if (Array.isArray( collection ) ) {
for ( var i = 0; i < collection.length; i++ ){
iterator( collection[i], i, collection );
}
} else {
for (var k in collection) {
iterator( collection[k], k, collection );
}
}
};-
Takes two arguments,
collection(an array or object), anditerator(a function) -
If it's an array - use a 'for loop'
- loop length of the array
- on each iteration, call
iteratorand pass in thevalue,keyandcollectionvalue= the current value -collection[i]key= the index -ithe thing you increment to traverse an arraycollection= the array or object you passed in
-
If it's an object - use a 'while loop'
- declare variable
kas your index - call
iteratorand pass in thevalue,keyandcollectionthe same way as above.
- declare variable
-
There is no return statement because ...? (the original array/object is modified)
Exactly the same as Lodash
_.each = function( collection, iterator ) {
if (Array.isArray( collection )) {
for ( var i = 0; i < collection.length; i++ ){
iterator( collection[i], i, collection );
}
} else {
for ( var k in collection ) {
iterator( collection[k], k, collection );
}
}
};Okay... this seems like a bit much for something that already comes with JavaScript, but this guy wants to do it his way, and I can get down with that. I'm not at the point yet where I quite understand the whole 'more functional' point of view, but either way, I'm happy to see something different.
In fn.each() he uses fn.apply() and fn.concat(), which seem to do the same thing the native Array.prototype methods do.
fn.apply = function ( handler, args) {
return handler.apply( null, args);
};
fn.concat = function () {
var args = fn.toArray(arguments);
var first = args[ 0 ];
if (!fn.is('array', first) && !fn.is('string', first)) {
first = args.length ? [ first ] : [ ];
}
return first.concat.apply(first, args.slice(1));
};
//* */* */* */* */ fn.each
fn.each = function ( handler, collection, params ) {
for ( var index = 0, collectionLength = collection.length; index < collectionLength; index++) {
fn.apply( handler, fn.concat( [ collection[ index ], index, collection ], params));
}
};fn.eachtakes ahandler, acollectionandparamshandleris a function (???)collectionis an array (maybe also an object)paramsis an array of arguments (??)
- loop through length of the collection
- and on each iteration, call this...
fn.apply( handler, fn.concat( [ collection[ index ], index, collection ], params));- Apply takes a handler and an array of arguments - so everything after handler is building an array or arguments
- The handler function is called passing in the right arguments.
function forEach(array, fn) {
var i = -1,
len = array.length;
while ( ++i < len ) {
if ( fn( array[i], i) === false) {
return false;
}
}
return true;
}Not quite sure how this is assigning values to anything. I'll come back to this...
- Iterates over a collection, calling a function on each item and projecting the result into a new aray.
- Takes one argument, a
callback(function), with the option to include an argument to specify thethiskeyword (object). - Returns a new array.
Syntax
Array.prototype.map( callback( value, key, collection ) [optional: this] );function _map( fn, functor ){
var idx = 0;
var len = functor.length;
var result = Array( len );
while ( idx < len ){
result [idx] = fn( functor [idx] );
idx += 1;
}
return result;
};- Takes two arguments - fn, a
function, andfunctor, an array - Create an empty array the same length as
functorand assign it to variableresults- if i'm mapping an array 4 items long, i create an array called
resultthat looks like
- if i'm mapping an array 4 items long, i create an array called
result = [ undefined, undefined, undefined, undefined ]- Instead of creating an empty array and then later pushing items into it, we're assigning values this list of
undefined's
- use a while loop, and on each element in the
functorarray, change the value of one of myundefineds to the return value offncalled on the next item in thefunctorarray
result [idx] = fn( functor [idx] );This is the way Jafar teaches it in his course on Frontend Masters (and the learnRx online exercises)
Array.prototype.map = function( projectionFunction ) {
var results = [];
this.forEach( function( itemInArray ) {
results.push( projectionFunction( itemInArray ));
});
return results;
};- Declare
results, an empty variable. - call
forEach()onthisitemInArrayas thevalueargument - The current element being processed in the array.- On each item in the array,
- push into
resultsarray- the return value created by callng your
projectionFunctionpassing in the current value
- the return value created by callng your
- Return
results
_.map = _.collect = function(obj, iteratee, context) {
iteratee = cb(iteratee, context);
var keys = !isArrayLike(obj) && _.keys(obj),
length = (keys || obj).length,
results = Array(length);
for (var index = 0; index < length; index++) {
var currentKey = keys ? keys[index] : index;
results[index] = iteratee(obj[currentKey], currentKey, obj);
}
return results;
}; _.map = function(collection, iterator) {
if (Array.isArray(collection)) {
var result = [];
for (var i = 0; i < collection.length; i++){
result.push(iterator(collection[i], i, collection));
}
return result;
} else {
var result = {};
for (var k in collection) {
result[k] = iterator(collection[k], k, collection);
}
return result;
}fn.map = function (handler, collection, params) {
return fn.reduce(function (accumulator, value, index) {
accumulator.push( fn.apply(handler, fn.concat([ value, index, collection ], params)) );
return accumulator;
}, [ ], collection);
};Returns an array of only the values that pass a test.
Takes one argument, a callback (function) that expects to return a Boolean, and an option to specify the this keyword.
The callback takes the standard value, key, collection arguments.
Array.prototype.filter = function( predicateFunction ) {
var results = [];
this.forEach( function( itemInArray )){
if ( predicateFunction( itemInArray )) {
results.push( itemInArray );
}
});
return results;
};_.filter = _.select = function(obj, predicate, context) {
var results = [];
predicate = cb( predicate, context );
_.each( obj, function( value, index, list) {
if ( predicate( value, index, list )) results.push( value );
});
return results;
};_.filter = function( collection, test ) {
var result = [];
_.each( collection, function( item ) {
if (test( item )) {
result.push( item );
}
});
return result;
};Oh goodness. I'll get back to this one...
var filter = _curry2(_dispatchable('filter', _xfilter, function ( pred, filterable) {
return _isObject( filterable) ? _reduce( function ( acc, key) {
if ( pred( filterable[key])) {
acc[key] = filterable[key];
}
return acc;
}, {}, keys(filterable)) : // else
_filter(pred, filterable);
}));fn.filter = function ( expression, collection ) {
return fn.reduce( function ( accumulator, item, index ) {
expression( item, index ) && accumulator.push( item );
return accumulator;
}, [ ], collection);
};Takes one argument callback - an aggregation function. This callback takes the same paramaters as the others value, key, collection, except they come AFTER an accumulator. The accumulator is the aggrigate value as reduce moves through a collection. The is also the option of providing an initialValue - where in the array to start - if none is given, start at the begining and reduce the entire array into a single value.
Syntax
arr.reduce( callback( accumulator, value, key, collection ) [initialValue] )Array.prototype.reduce = function( combiner, initialValue ) {
var counter, accumulatedValue;
if (this.length === 0) {
return this;
}
else {
if (arguments.length === 1) {
counter = 1;
accumulatedValue = this[0];
else if {
(arguments.length >= 2) {
counter = 0;
accumulatedValue = initialValue;
}
else {
throw "Invalid arguments.";
}
while (counter < this.length) {
accumulatedValue = combiner(accumulatedValue, this[counter] )
counter++;
}
return [accumulatedValue];
}
}
}
}- If the array is empty, do nothing
- If the user didn't pass an initial value, use the first item.
- If there are two arguments (the first is
combinerand second isinitalValue)- set
counterto 0, and - set
accumulatedValueto the value of the theinitialValue
- set
- The first argument
combineris a function that takes in theaccumulated valueand the current item in the array. - Loop through the array, feeding the current value and the result of the previous computation back into the
combinerfunction until we've exhausted the entire array and are left with only one item. - Return the final
accumulatedValueinside an array (one item long)
_.reduce = function(collection, iterator, accumulator) {
var result = accumulator === undefined ? collection[0] : accumulator;
_.each(collection, function(item) {
result = iterator(result, item);
});
return result;
};fn.reduce = function (handler, accumulator, collection, params) {
fn.each(function (value, index) {
accumulator = fn.apply(handler, fn.concat([ accumulator, value, index ], params));
}, collection);
return accumulator;
};