Created
April 19, 2017 16:13
-
-
Save AndreasLoukakis/d660b3442b839ed74f7b1c610a598ebe to your computer and use it in GitHub Desktop.
Various currying implementations, for reference an comparison
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// @Underscore | |
// Partially apply a function by creating a version that has had some of its | |
// arguments pre-filled, without changing its dynamic `this` context. _ acts | |
// as a placeholder by default, allowing any combination of arguments to be | |
// pre-filled. Set `_.partial.placeholder` for a custom placeholder argument. | |
_.partial = restArgs(function(func, boundArgs) { | |
var placeholder = _.partial.placeholder; | |
var bound = function() { | |
var position = 0, length = boundArgs.length; | |
var args = Array(length); | |
for (var i = 0; i < length; i++) { | |
args[i] = boundArgs[i] === placeholder ? arguments[position++] : boundArgs[i]; | |
} | |
while (position < arguments.length) args.push(arguments[position++]); | |
return executeBound(func, bound, this, this, args); | |
}; | |
return bound; | |
}); | |
// @Ramda | |
/** | |
* @private | |
* @category Function | |
* @param {Function} fn The function to curry. | |
* @return {Function} The curried function. | |
*/ | |
var _curry1 = function _curry1(fn) { | |
return function f1(a) { | |
if (arguments.length === 0 || _isPlaceholder(a)) { | |
return f1; | |
} else { | |
return fn.apply(this, arguments); | |
} | |
}; | |
}; | |
/** | |
* @private | |
* @category Function | |
* @param {Function} fn The function to curry. | |
* @return {Function} The curried function. | |
*/ | |
var _curry2 = function _curry2(fn) { | |
return function f2(a, b) { | |
switch (arguments.length) { | |
case 0: | |
return f2; | |
case 1: | |
return _isPlaceholder(a) ? f2 : _curry1(function (_b) { | |
return fn(a, _b); | |
}); | |
default: | |
return _isPlaceholder(a) && _isPlaceholder(b) ? f2 : _isPlaceholder(a) ? _curry1(function (_a) { | |
return fn(_a, b); | |
}) : _isPlaceholder(b) ? _curry1(function (_b) { | |
return fn(a, _b); | |
}) : fn(a, b); | |
} | |
}; | |
}; | |
/** | |
* @private | |
* @category Function | |
* @param {Function} fn The function to curry. | |
* @return {Function} The curried function. | |
*/ | |
var _curry3 = function _curry3(fn) { | |
return function f3(a, b, c) { | |
switch (arguments.length) { | |
case 0: | |
return f3; | |
case 1: | |
return _isPlaceholder(a) ? f3 : _curry2(function (_b, _c) { | |
return fn(a, _b, _c); | |
}); | |
case 2: | |
return _isPlaceholder(a) && _isPlaceholder(b) ? f3 : _isPlaceholder(a) ? _curry2(function (_a, _c) { | |
return fn(_a, b, _c); | |
}) : _isPlaceholder(b) ? _curry2(function (_b, _c) { | |
return fn(a, _b, _c); | |
}) : _curry1(function (_c) { | |
return fn(a, b, _c); | |
}); | |
default: | |
return _isPlaceholder(a) && _isPlaceholder(b) && _isPlaceholder(c) ? f3 : _isPlaceholder(a) && _isPlaceholder(b) ? _curry2(function (_a, _b) { | |
return fn(_a, _b, c); | |
}) : _isPlaceholder(a) && _isPlaceholder(c) ? _curry2(function (_a, _c) { | |
return fn(_a, b, _c); | |
}) : _isPlaceholder(b) && _isPlaceholder(c) ? _curry2(function (_b, _c) { | |
return fn(a, _b, _c); | |
}) : _isPlaceholder(a) ? _curry1(function (_a) { | |
return fn(_a, b, c); | |
}) : _isPlaceholder(b) ? _curry1(function (_b) { | |
return fn(a, _b, c); | |
}) : _isPlaceholder(c) ? _curry1(function (_c) { | |
return fn(a, b, _c); | |
}) : fn(a, b, c); | |
} | |
}; | |
}; | |
/** | |
* @private | |
* @category Function | |
* @param {Number} length The arity of the curried function. | |
* @param {Array} received An array of arguments received thus far. | |
* @param {Function} fn The function to curry. | |
* @return {Function} The curried function. | |
*/ | |
var _curryN = function _curryN(length, received, fn) { | |
return function () { | |
var combined = []; | |
var argsIdx = 0; | |
var left = length; | |
var combinedIdx = 0; | |
while (combinedIdx < received.length || argsIdx < arguments.length) { | |
var result; | |
if (combinedIdx < received.length && (!_isPlaceholder(received[combinedIdx]) || argsIdx >= arguments.length)) { | |
result = received[combinedIdx]; | |
} else { | |
result = arguments[argsIdx]; | |
argsIdx += 1; | |
} | |
combined[combinedIdx] = result; | |
if (!_isPlaceholder(result)) { | |
left -= 1; | |
} | |
combinedIdx += 1; | |
} | |
return left <= 0 ? fn.apply(this, combined) : _arity(left, _curryN(length, combined, fn)); | |
}; | |
}; | |
var curryN = _curry2(function curryN(length, fn) { | |
if (length === 1) { | |
return _curry1(fn); | |
} | |
return _arity(length, _curryN(length, [], fn)); | |
}); | |
var curry = _curry1(function curry(fn) { | |
return curryN(fn.length, fn); | |
}); | |
// @FPO | |
//FPO has a named parameters alternative, using destructuring. | |
function curry({ fn, n: arity = 1 }) { | |
arity = Number( arity ); | |
return (function nextCurried(prevArgsObj){ | |
return function curried(nextArgsObj = {}){ | |
var keys = Object.keys( nextArgsObj ); | |
var allArgsObj = (keys.length > 0) ? | |
Object.assign( {}, prevArgsObj, {[keys[0]]: nextArgsObj[keys[0]]} ) : | |
prevArgsObj; | |
if (Object.keys( allArgsObj ).length >= arity) { | |
return fn( allArgsObj ); | |
} | |
else { | |
return nextCurried( allArgsObj ); | |
} | |
}; | |
})( {} ); | |
} | |
function stdCurry(fn,arity = Math.max( 1, fn.length)) { | |
arity = Number( arity ); | |
return (function nextCurried(prevArgs){ | |
return function curried(...nextArgs){ | |
var allArgs = (nextArgs.length > 0) ? | |
prevArgs.concat( [nextArgs[0]] ) : | |
prevArgs; | |
if (allArgs.length >= arity) { | |
return fn( ...allArgs ); | |
} | |
else { | |
return nextCurried( allArgs ); | |
} | |
}; | |
})( [] ); | |
} | |
function curryMultiple({ fn, n: arity = 1 }) { | |
arity = Number( arity ); | |
return (function nextCurried(prevArgsObj){ | |
return function curried(nextArgsObj = {}){ | |
var allArgsObj = (Object.keys( nextArgsObj ).length > 0) ? | |
Object.assign( {}, prevArgsObj, nextArgsObj ) : | |
prevArgsObj; | |
if (Object.keys( allArgsObj ).length >= arity) { | |
return fn( allArgsObj ); | |
} | |
else { | |
return nextCurried( allArgsObj ); | |
} | |
}; | |
})( {} ); | |
} | |
function stdCurryMultiple(fn,arity = Math.max( 1, fn.length)) { | |
arity = Number( arity ); | |
return (function nextCurried(prevArgs){ | |
return function curried(...nextArgs){ | |
var allArgs = (nextArgs.length > 0) ? | |
prevArgs.concat( nextArgs ) : | |
prevArgs; | |
if (allArgs.length >= arity) { | |
return fn( ...allArgs ); | |
} | |
else { | |
return nextCurried( allArgs ); | |
} | |
}; | |
})( [] ); | |
} | |
// http://stackoverflow.com/questions/5176313/javascript-curry | |
// define the curry() function | |
function curry(fn, scope) { | |
// set the scope to window (the default global object) if no scope was passed in. | |
scope = scope || window; | |
// Convert arguments into a plain array, because it is sadly not one. | |
// args will have all extra arguments in it, not including the first 2 (fn, scope) | |
// The loop skips fn and scope by starting at the index 2 with i = 2 | |
var args = []; | |
for (var i = 2, len = arguments.length; i < len; ++i) { | |
args.push(arguments[i]); | |
} | |
// Create the new function to return | |
return function() { | |
// Convert any arguments passed to the this function into an array. | |
// This time we want them all | |
var args2 = []; | |
for (var i = 0; i < arguments.length; i++) { | |
args.push(arguments[i]); | |
} | |
// Here we combine any args originally passed to curry, with the args | |
// passed directly to this function. | |
// curry(fn, scope, a, b)(c, d) | |
// would set argstotal = [a, b, c, d] | |
var argstotal = args.concat(args2); | |
// execute the original function being curried in the context of "scope" | |
// but with our combined array of arguments | |
return fn.apply(scope, argstotal); | |
}; | |
} | |
// Simplest one: | |
function curry(fn) { | |
var c = curry.bind(this, fn = fn.bind.apply(fn, [this].concat([].slice.call(arguments, 1)))); | |
c.exec = fn; | |
return c; | |
} | |
curry(sum, 1, 2)(3)(4, 5)(6, 7, 8).exec(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment