Skip to content

Instantly share code, notes, and snippets.

@masahirompp masahirompp/fun.js
Created Feb 4, 2015

Embed
What would you like to do?
utils
'use strict';
var _ = window._;
/**
*
* @param thing
*/
function fail(thing) {
throw new Error(thing);
}
/**
* 存在確認
* @param x
* @returns {boolean}
* @description nullとundefinedのみfalseとなる。false,0,''はtrueとなる。
* @example
* existy(null) => false
* existy(undefind) => false
* existy(false) => true
* existy(0) => true
* truthy('') => true
*/
function existy(x) {
return x != null;
}
/**
* 真偽値確認
* @param x
* @returns {boolean}
* @description 0と''もtrueとなる。null,undefined,falseのみfalseとなる。
* @example
* truthy(null) => false
* truthy(undefind) => false
* truthy(false) => false
* truthy(0) => true
* truthy('') => true
*/
function truthy(x) {
return (x !== false) && existy(x);
}
/**
* カンマ区切りの引数を期待する関数に、配列で引数を渡すようにする。
* @param fun
* @returns {Function}
* @example
* fun(1,2,3) => A
* splat(fun)([1,2,3]) => A
*/
function splat(fun) {
return function(array) {
return fun.apply(null, array);
};
}
/**
* 引数として配列を期待する関数に、カンマ区切りで引数を渡すように変換する
* @param fun
* @returns {Function}
* @example
* fun([1,2,3]) => A
* unsplat(fun)(1,2,3) => A
*/
function unsplat(fun) {
return function() {
return fun.call(null, _.toArray(arguments));
};
}
/**
* condがtrueの場合のみactionを実行する
* @param cond
* @param action
* @returns {*}
*/
function doWhen(cond, action) {
if(truthy(cond)) {
return action();
}
return void 0;
}
/**
* 逆を返す関数を返す
* @param pred
* @returns {Function}
*/
function complement(pred) {
return function() {
return !pred.apply(null, _.toArray(arguments));
};
}
/**
* 真偽値反転
* @param x
* @returns {boolean}
*/
function not(x) {
return !x;
}
/**
* 配列の連結
* @args [[*]...]
* @returns {*}
* @example
* cat([1,2], [3,4], [5,6,7])
* => [1,2,3,4,5,6,7]
*/
function cat(/* いくつかの配列 */) {
var head = _.first(arguments);
if(existy(head)) {
return head.concat.apply(head, _.rest(arguments));
}
return [];
}
/**
* mapしてから配列の連結
* @param fun mapの高階関数
* @param coll [[*]|*...] 配列また要素の配列
* @returns {*}
* @example
* mapcat(e => construct(e, ['a']), [1,2,3]))
* => [1,'a',2,'a',3,'a']
*/
function mapcat(fun, coll) {
return cat.apply(null, _.map(coll, fun));
}
/**
* 配列の先頭に追加
* @param head
* @param tail
* @returns {*}
* @example
* construct(42, [1,2,3])
* => [42,1,2,3]
*/
function construct(head, tail) {
return cat([head], _.toArray(tail));
}
/**
* 特定のフィールドを返す関数を返す
* @param field
* @returns {Function}
*/
function plucker(field) {
return function(obj) {
return (obj && obj[field]);
};
}
/**
* 最も適切な値を返す。maxやminなどの抽象。
* @param fun : x => y => boolean 比較関数
* @param coll
* @returns {*}
*/
function best(fun, coll) {
return _.reduce(coll, function(x, y) {
return fun(x, y) ? x : y;
});
}
/**
* 特定回数繰り返す
* @param times
* @param fun
* @returns {*|Array}
*/
function repeatedly(times, fun) {
return _.map(_.range(times), fun);
}
/**
* 常に同じ値(関数)を返す関数を返す
* @param value
* @returns {Function}
*/
function always(value) {
return function() {
return value;
};
}
/**
* 対象オブジェクト(target)でメソッドを実行する関数を返す。
* @param name
* @param method
* @returns {Function}
* @example
* var rev = invoker('reverse',Array.prototype.reverse);
* _.map([[1,2,3],[4,5]],rev)
* => [[3,2,1],[5,4]]
*/
function invoker(name, method) {
return function(target /* ,args... */) {
if(!existy(target)) {
fail('Must provide a target');
}
var targetMethod = target[name];
var args = _.rest(arguments);
return doWhen((existy(targetMethod) && method === targetMethod), function() {
return targetMethod.apply(target, args);
});
};
}
/**
* 引数の既定値を設定した関数を返す
* @param fun
* @returns {Function}
*/
function fnull(fun /* ,defaults... */) {
var defaults = _.rest(arguments);
return function(/* args... */) {
var args = _.map(arguments, function(e, i) {
return existy(e) ? e : defaults[i];
});
return fun.apply(null, args);
};
}
/**
* ポリモーフィックな関数を生成
* @returns {Function}
* @example
* 引数の型(StringArray)によって計算方法を変える関数を返す例
* var str = dispatch(invoker('toString', Array.prototype.toString),
* invoker('toString', String.prototype.toString));
* str('a')
* => 'a'
* str([1,2,3,4])
* => '1,2,3,4'
*/
function dispatch(/* functions... */) {
var funs = _.toArray(arguments);
var size = funs.length;
/* このtargetはinvokerのtargetに相当する */
return function(target /*, args... */) {
var result, args = _.rest(arguments);
for(var i = 0; i < size; i++) {
var fun = funs[i];
result = fun.apply(fun, construct(target, args));
if(existy(result)) {
return result;
}
}
return result;
};
}
/**
* 引数1つのカリー化
* @param fun
* @returns {Function}
*/
function curry(fun) {
return function(arg) {
return fun(arg);
};
}
/**
* 引数2つのカリー化(右から)
* @param fun
* @returns {Function}
*/
function curry2(fun) {
return function(secondArg) {
return function(firstArg) {
return fun(firstArg, secondArg);
};
};
}
/**
* 引数3つのカリー化(右から)
* @param fun
* @returns {Function}
*/
function curry3(fun) {
return function(thirdArg) {
return function(secondArg) {
return function(firstArg) {
return fun(firstArg, secondArg, thirdArg);
};
};
};
}
/*
*/
/**
* より大きい
* @type {Function}
* @example
* var greaterThan10 = greaterThan(10);
* greaterThan10(15) => true;
*/
var greaterThan = curry2(function(l, r) {
return l > r;
});
/**
* より小さい
* @type {Function}
* @example
* lessThan(10)(11) => false
*/
var lessThan = curry2(function(l, r) {
return l < r;
});
/**
* 部分適用
* function.bindで代用可能
* @param fun
* @returns {Function}
*/
function partial(fun /* ,pargs... */) {
var pargs = _.rest(arguments);
return function(/* args... */) {
var args = cat(pargs, _.toArray(arguments));
return fun.apply(fun, args);
};
}
/**
* 検証関数
* @param message
* @param fun
* @returns {Function}
*/
function validator(message, fun) {
var f = function(/* args... */) {
return fun.apply(fun, arguments);
};
f.message = message;
return f;
}
/**
* condition1の「1」は引数1つ(=arg)の意味
* argに対してvalidatorsを実施し、エラーがなければfunを実行する関数を返す。
* @returns {Function}
*/
function condition1(/* validators */) {
var validators = _.toArray(arguments);
return function(fun, arg) {
var errors = mapcat(function(isValid) {
return isValid(arg) ? [] : [isValid.message];
}, validators);
if(!_.isEmpty(errors)) {
return fail(errors.join(' '));
}
return fun(arg);
};
}
/**
* argsに対してpredsがすべて真の場合trueを返す関数を返す。
* @returns {Function}
* @example
* andify(isNumber, isEven)(1,2,3) => false
* andify(isNumber, isEven)(2,4,6) => true
*/
function andify(/* preds */) {
var preds = _.toArray(arguments);
return function(/* args */) {
var args = _.toArray(arguments);
var everything = function(ps, truth) {
if(_.isEmpty(ps)) {
return truth;
}
return _.every(args, _.first(ps)) && everything(_.rest(ps), truth);
};
return everything(preds, true);
};
}
/**
* argsに対するpredsが1つでも真の場合trueを返す関数を返す。
* @returns {Function}
* @example
* orify(idOdd, isZero)() => false;
* orify(idOdd, isZero)(0,2,4,6) => true;
* orify(idOdd, isZero)(2,4,6) => false;
*/
function orify(/* preds */) {
var preds = _.toArray(arguments);
return function(/* args */) {
var args = _.Array(arguments);
var something = function(ps, truth) {
if(_.isEmpty(ps)) {
return truth;
}
return _.some(args, _.first(ps)) || something(_.rest(ps), truth);
};
return something(preds, false);
};
}
/**
* 配列の再帰的なフラット化
* @param array
* @returns {*[]}
* @example
* flat([[1,2],[3,4,[5,[6],[7,8]]]]) => [1,2,3,4,5,6,7,8]
*/
function flat(array) {
if(_.isArray(array)) {
cat.apply(cat, _.map(array, flat));
}
return [array];
}
/**
*
* @param mapFun
* @param resultFun
* @param array
* @returns {*}
*/
function visit(mapFun, resultFun, array) {
if(_.isArray(array)) {
return resultFun(_.map(array, mapFun));
}
return resultFun(array);
}
/**
*
* @param fun
* @param ary
* @returns {*}
*/
function postDepth(fun, ary) {
return visit(partial(postDepth, fun), fun, ary);
}
/**
*
* @param fun
* @param ary
* @returns {*}
*/
function preDepth(fun, ary) {
return visit(partial(postDepth, fun), fun, fun(ary));
}
/**
* 元のデータを変更しないextend
* @returns {void|*}
*/
function merge(/* args */) {
return _.extend.apply(null, construct({}, arguments));
}
/**
* 遅延評価メソッドチェーン
* @param obj
* @constructor
* @example
* new LazyChain([2,1,3]).invoke('sort').force();
* => [1,2,3]
*/
function LazyChain(obj) {
var isLC = (obj instanceof LazyChain);
this._calls = isLC ? cat(obj._calls, []) : [];
this._target = isLC ? obj._target : obj;
}
/**
* 実行するメソッド
* @param methodName
* @returns {LazyChain}
*/
LazyChain.prototype.invoke = function(methodName /* args */) {
var args = _.rest(arguments);
this._calls.push(function(target) {
var meth = target[methodName];
return meth.apply(target, args);
});
return this;
};
/**
* 評価
* @returns {*}
*/
LazyChain.prototype.force = function() {
return _.reduce(this._calls, function(target, thunk) {
return thunk(target);
}, this._target);
};
/**
* _.tapと同じ位置づけ
* @param fun
* @returns {LazyChain}
*/
LazyChain.prototype.tap = function(fun) {
this._calls.push(function(target) {
fun(target);
return target;
});
return this;
};
/*
不変なメソッドチェーンの例
*/
function Point(x, y) {
this._x = x;
this._y = y;
}
Point.prototype.withX = function(val) {
return new Point(val, this._y);
};
Point.prototype.withY = function(val) {
return new Point(this._x, val);
};
(new Point(0, 1)).withX(100).withY(1000);
// => { _x: 100, _y: 1000}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.