モナドって何?
値を入れるための箱、くらいの認識でも使える。本稿ではモナドとしてTask
とMaybe
を使うが詳細を知らなくていい。JavaScriptでモナド(など)を実現するための仕様としてfantasy-landというものがあり、それを参考に実装されたramdaとfolktaleというライブラリを使う。
const R = require('ramda');
const Maybe = require('folktale/maybe');
const Task = require('folktale/concurrency/task');
MaybeとTaskに入った値の確認方法を書いておく。以下の章で特に説明なく使う。
Maybe.of(20).map(console.log); // 20 がプリントされる
Task.of(10).run().promise().then(console.log); // 10 がプリントされる
環境によってはconsole.log
をconsole.log.bind(console)
にしなければいけないかも。
map: 保持する値を処理
具体例: 10を2回インクリメントすると12になる。(モナドの保持する)値を変更する関数をmap
に渡すと、モナドの値を変更してくれる。返り値はモナドになるので、メソッドチェーンで処理をつなげることができる。
{
const ten = Maybe.of(10);
const incr = val => val + 1;
ten.map(incr).map(incr).map(console.log);
// pointfree version
const app = R.map(R.compose(console.log, incr, incr));
app(ten);
}
chain: 保持する値を処理~その2~
具体例: 6を2回インクリメントすると8になる。mapの場合、渡す関数はval => val + 1
のように関数自体はモナドを意識しない。モナドが自動的にmap
の返り値をモナドにしてくれているわけだ。一方、渡す関数自体がモナドを意識した実装にすることがある。たとえば、val => Maybe.of(val +1)
。この関数をmap
に渡してしまうと、モナドのモナドが帰ってしまう。それを避けるために、chain
を使う。chain
を使うと、モナドは返り値をモナドにしない。
// chain/flatMap
{
const six = Maybe.of(6);
const mIncr = val => (val ? Maybe.of(val + 1) : Maybe.Nothing());
six.chain(mIncr).chain(mIncr).map(console.log);
// pointfree version
const app = R.compose(R.map(console.log), R.chain(mIncr), R.chain(mIncr));
app(six);
}
ap, lift: モナドとモナドの処理
具体例: 2 + 3 = 5。二つのモナドの値同士を合わせて何かの処理をしたい場合、map
とchain
を組み合わせてパズルのように計算することもできるが、より簡単な方法がある。liftingという技術を使う。(a, b) => a + b
のようなモナドを意識しない関数であっても、これをliftしてあげると、引数をモナドにしたときに動くようになる。また、関数自体をモナドにして、ap
でモナドを渡していく、という技術もある。
// ap and lift
{
const two = Maybe.of(2);
const three = Maybe.of(3);
three.map(two.chain(R.add)).map(console.log);
R.lift(R.add)(two, three).map(console.log);
Maybe.of(R.add).ap(two).ap(three).map(console.log);
// pointfree version
const app = R.curry(R.compose(R.map(console.log), R.lift(R.add)));
app(two)(three);
}
sequence/traverse: リストのモナド
具体例: [Task.of(20), Task.of(40)] -> Task.of([20, 40])
モナドのリストよりもリストのモナドの方が扱いやすいことがある。その場合はsequence
を使う。モナド同士が独立なので演算をしないけど一つのモナドにしたい、とかのユースケース。
// sequence
R.sequence(Task.of)([Task.of(20), Task.of(40)])
.run().promise().then(console.log); // -> [20, 40]
リストのモナドを、traverse
を使って直接作ることもできる。リストにmap
をしてもリストのモナドのままであるが、リストにtraverse
をすると、モナドのリストになる。
const twice = val => Task.of(2 * val);
R.traverse(Task.of)(twice)([10, 20])
.run().promise().then(console.log); // -> [20 ,40]