oboe.js-demo
Version:
A website for Oboe.js
251 lines (206 loc) • 5.99 kB
JavaScript
/**
* Partially complete a function.
*
* var add3 = partialComplete( function add(a,b){return a+b}, 3 );
*
* add3(4) // gives 7
*
* function wrap(left, right, cen){return left + " " + cen + " " + right;}
*
* var pirateGreeting = partialComplete( wrap , "I'm", ", a mighty pirate!" );
*
* pirateGreeting("Guybrush Threepwood");
* // gives "I'm Guybrush Threepwood, a mighty pirate!"
*/
var partialComplete = varArgs(function( fn, args ) {
// this isn't the shortest way to write this but it does
// avoid creating a new array each time to pass to fn.apply,
// otherwise could just call boundArgs.concat(callArgs)
var numBoundArgs = args.length;
return varArgs(function( callArgs ) {
for (var i = 0; i < callArgs.length; i++) {
args[numBoundArgs + i] = callArgs[i];
}
args.length = numBoundArgs + callArgs.length;
return fn.apply(this, args);
});
}),
/**
* Compose zero or more functions:
*
* compose(f1, f2, f3)(x) = f1(f2(f3(x))))
*
* The last (inner-most) function may take more than one parameter:
*
* compose(f1, f2, f3)(x,y) = f1(f2(f3(x,y))))
*/
compose = varArgs(function(fns) {
var fnsList = arrayAsList(fns);
function next(params, curFn) {
return [apply(params, curFn)];
}
return varArgs(function(startParams){
return foldR(next, startParams, fnsList)[0];
});
});
/**
* A more optimised version of compose that takes exactly two functions
* @param f1
* @param f2
*/
function compose2(f1, f2){
return function(){
return f1.call(this,f2.apply(this,arguments));
}
}
/**
* Generic form for a function to get a property from an object
*
* var o = {
* foo:'bar'
* }
*
* var getFoo = attr('foo')
*
* fetFoo(o) // returns 'bar'
*
* @param {String} key the property name
*/
function attr(key) {
return new Function('o', 'return o["' + key + '"]' );
}
/**
* Call a list of functions with the same args until one returns a
* truthy result. Similar to the || operator.
*
* So:
* lazyUnion([f1,f2,f3 ... fn])( p1, p2 ... pn )
*
* Is equivalent to:
* apply([p1, p2 ... pn], f1) ||
* apply([p1, p2 ... pn], f2) ||
* apply([p1, p2 ... pn], f3) ... apply(fn, [p1, p2 ... pn])
*
* @returns the first return value that is given that is truthy.
*/
lazyUnion = varArgs(function(fns) {
return varArgs(function(params){
var maybeValue;
for (var i = 0; i < len(fns); i++) {
maybeValue = apply(params, fns[i]);
if( maybeValue ) {
return maybeValue;
}
}
});
});
/**
* This file declares various pieces of functional programming.
*
* This isn't a general purpose functional library, to keep things small it
* has just the parts useful for Oboe.js.
*/
/**
* Call a single function with the given arguments array.
* Basically, a functional-style version of the OO-style Function#apply for
* when we don't care about the context ('this') of the call.
*
* The order of arguments allows partial completion of the arguments array
*/
function apply(args, fn) {
return fn.apply(undefined, args);
}
/**
* Define variable argument functions but cut out all that tedious messing about
* with the arguments object. Delivers the variable-length part of the arguments
* list as an array.
*
* Eg:
*
* var myFunction = varArgs(
* function( fixedArgument, otherFixedArgument, variableNumberOfArguments ){
* console.log( variableNumberOfArguments );
* }
* )
*
* myFunction('a', 'b', 1, 2, 3); // logs [1,2,3]
*
* var myOtherFunction = varArgs(function( variableNumberOfArguments ){
* console.log( variableNumberOfArguments );
* })
*
* myFunction(1, 2, 3); // logs [1,2,3]
*
*/
function varArgs(fn){
var numberOfFixedArguments = fn.length -1,
slice = Array.prototype.slice;
if( numberOfFixedArguments == 0 ) {
// an optimised case for when there are no fixed args:
return function(){
return fn.call(this, slice.call(arguments));
}
} else if( numberOfFixedArguments == 1 ) {
// an optimised case for when there are is one fixed args:
return function(){
return fn.call(this, arguments[0], slice.call(arguments, 1));
}
}
// general case
// we know how many arguments fn will always take. Create a
// fixed-size array to hold that many, to be re-used on
// every call to the returned function
var argsHolder = Array(fn.length);
return function(){
for (var i = 0; i < numberOfFixedArguments; i++) {
argsHolder[i] = arguments[i];
}
argsHolder[numberOfFixedArguments] =
slice.call(arguments, numberOfFixedArguments);
return fn.apply( this, argsHolder);
}
}
/**
* Swap the order of parameters to a binary function
*
* A bit like this flip: http://zvon.org/other/haskell/Outputprelude/flip_f.html
*/
function flip(fn){
return function(a, b){
return fn(b,a);
}
}
/**
* Create a function which is the intersection of two other functions.
*
* Like the && operator, if the first is truthy, the second is never called,
* otherwise the return value from the second is returned.
*/
function lazyIntersection(fn1, fn2) {
return function (param) {
return fn1(param) && fn2(param);
};
}
/**
* A function which does nothing
*/
function noop(){}
/**
* A function which is always happy
*/
function always(){return true}
/**
* Create a function which always returns the same
* value
*
* var return3 = functor(3);
*
* return3() // gives 3
* return3() // still gives 3
* return3() // will always give 3
*/
function functor(val){
return function(){
return val;
}
}