2013 Minori Yamashita ympbyc@gmail.com
Underscoreに足りない関数を全部乗せ。
{} * {} * ... -> {}
[] * [] * ... -> []
[] * a * ... -> []
"" * "" * ... -> ""
それぞれのデータ型にふさわしい合成を行う.
_.conj({a:1, b:2}, {b:3, c:4}, {d:5}); //=> {a:1, b:3, c:4, d:5} //mergeと同じ
_.conj([1,2,3], [4,5]); //=> [1,2,3,4,5] //concatと同じ
_.conj([1,2,3], 4, 5); //=> [1,2,3,4,5] //concatと同じ
_.conj("hel", "lo", "!"); //=> "hello!" //concatと同じ
{a} * (a -> b) -> [b]
[a] * (a -> b) -> [b]
ただのmap。keyを渡さない.
_.just_map([1,2,3], _[+](2)); //=> [3,4,5]
[a] * (a -> Boolean) * {} -> {}
[a] * (a -> Boolean) * {} * ({} * {} -> {}) -> {}
mapしてfindしてmerge
_.update_many([{a:1}, {a:4}, {a:2}], function(x) {return x.a < 3;}, {b:100});
//=> [{a:1, b:100}, {a:4}, {a:2, b:100}]
SequenceはStringやargumentsやArrayなど、lengthプロパティを持ったオブジェクト.
[a] * Number -> [a]
[a] * Number * Number -> [a]
シーケンスSを取り、そのsliceメソッドを適用するか、Arrayにキャストしてからsliceメソッドを適用する.
_.slice([1,2,3,4,5], 2); //=> [3,4,5]
_.slice("abcde", 2, 4); //=> "cd"
_.slice({length: 4, 0:1, 1:2, 2:3, 3:4}, 2,4); //=> [3, 4]
[a] * [a] -> [a]
[a] * a -> [a]
[a] * a * a -> [a]
...
シーケンスSを取り、そのconcatメソッドを適用するか、Arrayにキャストしてからconcatメソッドを適用する.
_.concat([1,2,3], 4, 5 6); //=> [1,2,3,4,5,6]
_.concat([1,2,3], [4,5,6]); //=> [1,2,3,4,5,6]
[a] -> Number
シーケンスのlengthプロパティを読み出す
_.len([1,2,3]); //=> 3
_.len("abcde"); //=> 5
[String] * String -> String
配列のjoinメソッドを呼ぶ.
_.join(["aaa", "bbb", "ccc"], "~"); => "aaa~bbb~ccc"
[a] * Number -> [[a]]
配列Aを数値N個ずつに切り分ける
_.splat([1,2,3,4,5,6,7], 2); //=> [[1,2], [3,4], [5,6], [7]]
{} * {} -> {}
{} * {} * {} -> {}
...
2つ以上のオブジェクトをマージした新しいオブジェクトを返す. スロット名が衝突した場合、後ろのものが優先される.
_.merge({a:1, b:2, c:3},
{c:"a", d:"b", e:"f"},
{e: 8});
//=> {a:1, b:2, c:"a", d:"b", e:8}
{} -> Function a -> {a}
_.mapは配列を返すが、これはオブジェクトを返す.
_.mapmap({a:1, b:2, c:3}, _['+'](5)); //=> {a:6, b:7, c:8}
String -> {}
{} -> String
文字列が与えられたらJSON.parse、それ以外ならJSON.stringifyする.
_.json({a:1, b:[2, 3]}); //=> '{"a":1, "b":[2, 3]}'
_.json('{"a": 1, "b": [2, 3]}', {a:1, b:[2, 3]});
{} * String * a -> {}
{} * String * a * String * b -> {}
...
オブジェクトにプロパティを追加する
_.assoc({a:1, b:2}, "c", 3, "d", 4, "a", 100); //=> {a:100, b:2, c:3, d:4}
Function a * [] -> a
Function a * [] * {} -> a
関数Fと配列Vを取り、F.apply(null, V)
する.
第三引数にコンテキストCが与えられた場合は、 F.apply(C, V)
する.
_.apply(function (a, b, c) { return [a,b,c]; },
[1,2,3]); //=> [1,2,3]
(a * b -> c) -> (b * a -> c)
(a * b * c -> d) -> (b * a * c -> d)
...
関数の第一引数と第二引数の位置を入れ替える.
_.flip(_['-'])(10, 5); //=> -5
Function -> Function
Function * a -> Function
Function * a * b -> Function
...
flipとpartialをひとまとめにしたもの.
var join_with_sharp = _.flippar(_.join, "#");
join_with_sharp(["aaa", "bbb", "ccc"]); //=> "aaa#bbb#ccc"
a * (a -> b) -> b
a * (a -> b) * (b -> c) -> c
...
F#の|>
, Clojureの->>
_.pipe("hello, ",
_.flippar(_['+'], "world!"),
_.str.capitalize); //=> "Hello, world!"
(a -> Boolean) * (a -> b) * (a -> b) -> (a -> b)
ifの関数版. 述語関数1つと関数2つをとり、関数を返す。
f = _.iff(_.eq(5),
_['*'](2),
_.partial(_.identity, -1));
f(8); //=> -1
f(5); //=> 10
Number * Function -> Function
数値Nと関数Fを取り、関数Fへの引数のN+1番目以降を配列にしてFに渡す関数Gを返す。argumentsのスライシングを抽象化する。
var f = _.optarg(2, function (a, b, cs) {
return cs;
});
f(1, 2, 3, 4, 5, 6, 7); //=> [3, 4, 5, 6, 7]
(a * a -> a) -> (a * a * a * ... -> a)
a * a -> a
な二引数関数を無限に引数を取れる関数に変換する.
var add = _.bin_multi(function (a) { return a + b; });
add(1,2,3,4,5,6,7,8,9); //=> 45
String * Function -> Function
メソッド名Mと関数Fを取って、「オブジェクトOと引数Aを取って、オブジェクトOにメソッドMが定義されていれば引数Aにそれを適用し、そうでなければ関数FにオブジェクトOと引数Aを渡す関数」を返す.
var slice = _.native_absent("slice", function (o, start, end) {
var arr = _.toArray(o);
return arr.slice(start, end);
});
Number * Function -> Function
引数の数が数値Nより少なかったら関数Fに部分適用する。
_.eq = _.auto_partial(2, function (a, b) {
return a === b;
});
_.reject([1,5,2,5,3,5], _.eq(5)); //=> [1,2,3]
Function -> String
関数の名前(name)を返す
type Monad = {bind: Monad a * (a -> Monad b) -> Monad b,
return: a -> Monad a}
_.domonad :: Monad * Monad a * (a -> Monad b) -> Monad b
:: Monad * Monad a * (a -> Monad b) -> (b -> Monad c) -> Monad c
:: ...
returnとbindを使って面白い事をする
var listM = {
bind: _.flatMap,
return: function (x) { return [x]; }
};
_.domonad(listM, [1,2,3],
function (x) { return [x, x * 2, x * 3]; },
function (x) { return listM.return(x + "!"); }
); //=> ["1!","2!","3!","2!","4!","6!","3!","6!","9!"]
var maybeM = {
return: _.identity,
bind: function (m, f) {
if (_.isNull(m) || _.isUndefined(m))
return null;
return f(m);
}
};
_.domonad(maybeM, {a: {b: {c: 3}}},
_.flippar(_.at, "a"),
_.flippar(_.at, "b"),
_.flippar(_.at, "c")); //=> 3
_.domonad(maybeM, {a: {b: {c: 3}}},
_.flippar(_.at, "a"),
_.flippar(_.at, "nonexistentkey"),
_.flippar(_.at, "c")); //=> null
String * {} -> String
eval禁止などで_.templateが使えない時や、テンプレートにロジックがない場合に使いやすいテンプレーティング関数.
_.simple_template("quick {{ color }} {{animal}} is {{color}}",
{color: "brown", animal: "fox"});
//=> "quick brown fox is brown"
{} * String -> Function
オブジェクトとメソッド名を取り、メソッドを関数にして返す.
var toUpper = _.fn(String.prototype, "toUpperCase");
toUpper("hello"); //=> "HELLO"
演算子は関数合成や部分適用のときに扱い辛いので関数を提供する. 全て勝手に部分適用される。
用意されている関数:
_["+"], _["-"], _["*"], _["/"], _["%"],
_.and, _.or, _.not,
_.eq, _.neq, _.lt, _.gt, _.lte, _.gte,
_.at
_["+"](2,3); //=> 5
_.bin_multi(_["+"])(1,2,3,4,5); //=> 15
_.map([1,2,3,4], _["*"](2)); //=> [2,4,6,8]
_.filter([1,2,0,4,1,3,1], _.lt(2)); //=> [4,3]
_.bin_multi(_.at)({a: {b: {c: "xxx"}}}, "a", "b", "c"); //=> "xxx"
{} * Function -> {}
{} * Function * Function -> {}
...
モジュールを作る
var myModule = _.module(
{},
function hola () {
return "Hola!"
},
function hi () {
return "Hi!"
}
);
myModule.hola(); //=> "Hola!"
_.module(
myModule,
function hehe () { return "hehe"; }
);
myModule.hehe();
- 演算子のデフォルトでの多引数化はやめた。これは_.mapなどとの兼ね合い。
//before
_["+"](1,2,3,4,5); //=> 15
_.map([1,2,3], _.partial(_['+'], 5)); //=> ["61,2,3", "81,2,3", "101,2,3"]
//now
_["+"](1,2,3,4,5); //=> 3
_.bin_multi(_['+'])(1,2,3,4,5); /=> 15
_.map([1,2,3], _.partial(_['+'], 5)); //=> [6,7,8]
- 代わりに演算子はデフォルトで部分適用を可能にした。
//before
_.partial(_['+'], 4)(5); //=> 9
//now (in addition to the old method)
_['+'](4)(5); //=> 9
_.bin_multi(_['+'])(1)(2,3,4,5); //=> 15