JavaScript
Node.js
関数型プログラミング
ramda
folktale


モナドって何?

値を入れるための箱、くらいの認識でも使える。本稿ではモナドとしてTaskMaybeを使うが詳細を知らなくていい。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.logconsole.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。二つのモナドの値同士を合わせて何かの処理をしたい場合、mapchainを組み合わせてパズルのように計算することもできるが、より簡単な方法がある。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]