CoffeeScriptでUnderscore.jsと戯れる
CoffeeScriptとUnderscore.jsの組み合わせで関数型言語のようなリスト主体のプログラミングをすることができます。せっかくなので_.mixin
で_
オブジェクトを拡張してみます。
keysとvaluesからObjectを作成する_.pairs
Underscore.jsには、Objectから(1)キーだけを配列として取り出す_.keys
と、(2)値だけを配列として取り出す_.values
という関数があります。が、逆に、keysとvaluesからObjectを作成するような関数がありません。v1.4.xで_.objectとして追加されたようです。
ということで、やってみます。
_ = require "underscore" log = () -> console.log a for a in arguments _.mixin pair: (k, v) -> r = {}; r[k] = v; r pair2: (xs) -> _.pair xs[0], xs[1] extend2: (o1, o2) -> _(o1).extend o2 pairs: (ks, vs) -> _(_(_.zip ks, vs).map(_.pair2)).reduce _.extend2, {} # 普通に書いた場合 pairs = (ks, vs) -> ret = {} ret[k] = vs[i] for k, i in ks ret obj = a: 1, b: 2, c: 3, d: 4, e: 5 ks = _(obj).keys() vs = _(obj).values() r1 = _.pairs ks, vs r2 = pairs ks, vs log ks, vs, r1, r2
_.extend2
を作成している理由は、reduce*1の仕様に起因します。直接_.extend
を渡すと、第4引数に処理対象の配列自身が入るためです。
_ = require "underscore" t = [{a: 1}, {b: 2}, {c: 3}] # lは常にt。_.extendでは数値は無視される(なのでiは無視される)が、 _(t).reduce ((m, v, i, l) -> console.log m, v, i, l; _.extend m, v), {} # 配列に対しては有効 console.log _.extend {}, t # { '0': { a: 1 }, '1': { b: 2 }, '2': { c: 3 } }
話は戻って、pairsの部分ですが、_(_(_.zip ks, vs).map(_.pair2)).reduce _.extend2, {}
というのは、如何せんスマートじゃないので直します。Haskellとかでは合成関数で書くと良い感じになるので、Underscore.jsの_.compose
を使ってみます。
_ = require "underscore" log = () -> console.log a for a in arguments _.mixin pair: (k, v) -> r = {}; r[k] = v; r pair2: (xs) -> _.pair xs[0], xs[1] extend2: (o1, o2) -> _(o1).extend o2 #pairs: (ks, vs) -> _(_(_.zip ks, vs).map(_.pair2)).reduce _.extend2, {} pairs: (ks, vs) -> (_.compose ((v) -> _(v).reduce _.extend2, {}) , ((v) -> _(v).map(_.pair2)) , (_.zip) ) ks, vs obj = a: 1, b: 2, c: 3, d: 4, e: 5 ks = _(obj).keys() vs = _(obj).values() r = _.pairs ks, vs log r
微妙。。。引数の順番や、関数がカリー化されていない都合上、無名関数を使わざるをえないのと*2、_.compose
とCoffeeScriptの相性が微妙です。CoffeeScriptは関数の引数に無名関数が出てくると、途端に書きづらくなるのがネックですね。
さて、実はUnderscore.jsには_.chain
というのがあるので、配列を連続して処理したい時はコレを使うのがよさそうです。
_ = require "underscore" log = () -> console.log a for a in arguments _.mixin pair: (k, v) -> r = {}; r[k] = v; r pair2: (xs) -> _.pair xs[0], xs[1] extend2: (o1, o2) -> _(o1).extend o2 #pairs: (ks, vs) -> _(_(_.zip ks, vs).map(_.pair2)).reduce _.extend2, {} pairs: (ks, vs) -> _.chain(_.zip ks, vs).map(_.pair2).reduce(_.extend2, {}).value() obj = a: 1, b: 2, c: 3, d: 4, e: 5 ks = _(obj).keys() vs = _(obj).values() r = _.pairs ks, vs log r
だいぶ見やすくなったのではないでしょうか。
配列から、2n番目をキー、2n+1番目を値とするオブジェクトを作成する_.pairNext
やってみます。
_ = require "underscore" log = () -> console.log a for a in arguments _.mixin pair: (k, v) -> r = {}; r[k] = v; r pair2: (xs) -> _.pair xs[0], xs[1] extend2: (o1, o2) -> _(o1).extend o2 pairs: (ks, vs) -> _.chain(_.zip ks, vs).map(_.pair2).reduce(_.extend2, {}).value() pairNext: (xs) -> _ys = _.chain(_.zip _.range(xs.length), xs) [ks, vs] = _([0, 1]).map \ (i) -> _ys.filter((xs) -> xs[0] % 2 is i).map(_.last).value() _.pairs ks, vs obj = a: 1, b: 2, c: 3, d: 4, e: 5 ks = _(obj).keys() vs = _(obj).values() xs = _(_.zip ks, vs).flatten() r = _.pairNext xs log xs, r
Underscore.jsとCoffeeScriptと組み合わせれば、実にキモくスマートに書けます。
クラスをbindでスマートにする
以下の様なシチュエーションを考えます。
class Hoge fn1: (key, x) -> console.log key, x fn2: (key, x) -> console.log key, x fn3: (key, x) -> console.log key, x fn4: (key, x) -> console.log key, x bind: (key) -> return fn1: _.bind @fn1, @, key fn2: _.bind @fn2, @, key fn3: _.bind @fn3, @, key fn4: _.bind @fn4, @, key hoge = new Hoge foo = hoge.bind "foo" foo.fn1 1 foo.fn2 2 foo.fn3 3 foo.fn4 4
Hoge.bindでkeyを固定化することができます。fnXが増えた時に、個別に_.bindを書くのは嫌なので、exportしたい関数名を配列でまとめます。先ほどの_.pairNext
を使ってみます。
_ = require "underscore" log = () -> console.log a for a in arguments _.mixin pair: (k, v) -> r = {}; r[k] = v; r pair2: (xs) -> _.pair xs[0], xs[1] extend2: (o1, o2) -> _(o1).extend o2 pairs: (ks, vs) -> _.chain(_.zip ks, vs).map(_.pair2).reduce(_.extend2, {}).value() pairNext: (xs) -> _ys = _.chain(_.zip _.range(xs.length), xs) [ks, vs] = _([0, 1]).map \ (i) -> _ys.filter((xs) -> xs[0] % 2 is i).map(_.last).value() _.pairs ks, vs class Hoge fn1: (key, x) -> console.log key, x fn2: (key, x) -> console.log key, x fn3: (key, x) -> console.log key, x fn4: (key, x) -> console.log key, x bind: (key) -> _.chain(["fn1", "fn2", "fn3", "fn4"]).map((v) => [v, _(@[v]).bind(@, key)]).flatten().pairNext().value() hoge = new Hoge foo = hoge.bind "foo" foo.fn1 1; foo.fn2 2; foo.fn3 3; foo.fn4 4
ん〜キモい。誰得感満載ですね。普通に書くのが一番ですね。
class Hoge fn1: (key, x) -> console.log key, x fn2: (key, x) -> console.log key, x fn3: (key, x) -> console.log key, x fn4: (key, x) -> console.log key, x bind: (key) -> ret = {} ret[v] = _(@[v]).bind @, key for v in ["fn1", "fn2", "fn3", "fn4"] ret
Undersocre.jsは調子に乗って使うと、かえって冗長になるのが玉に瑕です。超注意。