Skip to content

Instantly share code, notes, and snippets.

@isaacs
Created November 12, 2009 07:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save isaacs/232700 to your computer and use it in GitHub Desktop.
Save isaacs/232700 to your computer and use it in GitHub Desktop.
(function () {
// transform the function such that you can fix any of the arguments,
// passing _ as a "placeholder".
// for example:
// function timesSquared (x, y) { return x * y * y }
// var times2Squared = _(timesSquared)( _, 2 ) or timesSquared._(_, 2);
// times2Squared(3) = timesSquared(3, 2) = 12
// In the _ified function, if all the spaces are not filled in, it returns
// another _ified function with the remaining spaces blank.
// More examples:
// f._(_,_) ==> curryable function requiring 2 args
// f._(_,_)(x) ==> f._(x,_) ==> function curried to 1 arg, requiring one more for execution
// f._(_)._(_) ==> passing _ as an arg does nothing, so this is the same as just f._(_)
// in general, doing ._() just needs to happen once. doing it a second time is unnecessary.
// f._(_)._(_,_) ==> passes _ as the first arg, then the second, so now it's the same as f._(_,_)
// f._(_,_)(_,_,_) ==> f._(_,_,_)
// f._(_,_,_)(1,_,2) ==> f._(1,_,2) awaiting second argument.
// f._(_,_,_)(1,_,2)(3) ==> f._(1,_,2)(3) ==> f(1,3,2)
// f._(_,1)(2,3,4) ==> f(2,1,3,4) additional args are appended to list.
// f._(___,_,1) ==> additional args go in the ___ member, if one is provided.
// f._(___,_,1)(2) ==> f._(___,2,1) right-hand curried with 2,1
// f._(___,_,1)(2)(3,4,5) ==> f._(___,2,1)(3,4,5) ==> f(3,4,5,2,1)
// f._(___,1,2) is just like Y.rbind(f, null, 1, 2)
// f._(1,___,_,2)(3,4,5) ==> f._(1,___,3,2)(4,5) ==> f(1,4,5,3,2)
// extra args will go into the first ___ that is found. so, if there are two, you have to call it twice.
// f._(___,___)(1,2)(3,4,5) ==> f._(1,2,___)(3,4,5) ==> f(1,2,3,4,5)
// without any _ or ___ in the arguments, it'll just execute the function.
// furthermore
// _(f) === f._
// _(f)(1,_) === f._(1,_)
// etc.
// But wait! There's more!
// f._(1,2,3) ==> ?
// No holes, so what happens?
// It doesn't quite make sense to have it just do the same thing as
// f(1,2,3)
// So, instead, it returns a callback that can take _ arguments.
// So:
// f._(1,2,3)(a,_,c)(b) === f(1,2,3,a,b,c)
// setTimeout(foo._(bar)),0) === setTimeout(function () { foo(bar) }, 0)
// f._()(1,_,2,___)(3)(4,5,6) === f._(1,3,2,___)(4,5,6) === f(1,3,2,4,5,6)
// f._(1,2)(___,3,4)(5,6) === f._(1,2,___,3,4)(5,6) === f(1,2,5,6,3,4)
// So how do I fix scope, you ask?
// With the ___!
// It works just like ___, but the first argument is used to set the scope.
// so,
// f.___(obj, 1, 2) ==> function () { return f.call(obj, 1, 2) }
// this doesn't execute immediately, because there's always at least one
// level of indirection (otherwise it's pointless, and you can always add ()
// if you really want it to execute right away.)
// ___(obj, f)(1, 2) is the similiar as that
// And of course, this works, too:
// f.___(obj, 1)(_, 2)(3) ==> f.call(obj, 1, 3, 2)
// expose it!
var GLOBAL = (typeof exports !== 'undefined') ? exports : this;
GLOBAL._ = Function.prototype._ = _;
GLOBAL.___ = Function.prototype.___ = ___;
_.toString = ___.toString = function () { return this.name };
function arr (a, i) { return Array.prototype.slice.call(a, i || 0) };
function shared (u, origArgs, argExpect, me, fn, scope) {
var args = arr(origArgs, argExpect);
if (typeof(me) !== "function") {
if (typeof(fn) !== "function") throw new TypeError(
"Invalid argument(s) to "+u.name
);
return function () {
return u.apply(fn, args.concat(arr(arguments,0)));
}
}
if (me === _ || me === ___) throw new Error(
"While I respect your appreciation for meta, underscoring an underscore is not allowed."
);
return curry(me, arr(origArgs, argExpect - 1), scope);
};
function _ (fn) { return shared(_, arguments, 1, this, fn) };
function ___ (scope, fn) { return shared(___, arguments, 2, this, fn, scope) };
function curry (fn, args, scope, call) {
for (
var i = 0, l = args.length; i < l; i ++
) if (
args[i] === _ || args[i] === ___
) return fixArgs(fn, args, scope);
if (call === _) {
return fn.apply(scope || this, args);
}
return function _ified () {
return curry(fn, args.concat(arr(arguments,0)), scope || this, _);
};
};
function fixArgs (fn, fixedArgs, scope) { return function _ified () {
var newArgs = [];
for (
var f = 0, fl = fixedArgs.length, a = 0, al = arguments.length;
f < fl;
f ++
) if (
fixedArgs[f] === _ // fill with 1
) newArgs.push(a < al ? arguments[a++] : _);
else if (
fixedArgs[f] === ___ // fill with the rest
) newArgs = newArgs.concat(
a < al ? arr(arguments, a)
: ___
), a = al;
else newArgs.push(fixedArgs[f]);
return curry.call(this, fn, newArgs, scope, _);
}};
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment