Edited at

恒等モナドで分かる、モナドの作り方入門(関手もやるよ!)

More than 1 year has passed since last update.


前置き

記事の修正点は、真摯に対応いたします。

よろしくお願いします。


モナドとは

関数型プログラミング上では、「クライスリ圏」における3則を満たすもののこと…


ってもっと簡単に(笑)

プログラミングが分かる人向けの説明にすると…

ある状態として保持しておくことの出来る計算機構を、

その言語や環境内部に実装したもの…

と言い換えると、あれってなる人はお察しの通り、

チューリング完全

ってやつです。


モナドの作り方

ある計算機構がモナドである場合、以下の関数を内部に保持しています。


  • of

  • bind

  • join

作る際には map(fmap・flatMapとも言う)も必要になります。

つまり、モナドはfunctor(関手)でもあります。


of

計算機構に対して値を代入します。

言語によっては構造体やオブジェクト、関数なども代入できます。

単なる関数実行の場合を例に上げます。


実装例

function Monad (v) {

return Object.create(Monad.prototype, {
v: {
configurable: true,
writable: true,
value: v
}
})
}


join

計算機構から値を取り出します。

単なる get の場合は、以下のようになります。


実装例

Object.assign(Monad.prototype, {

get join () {
return this.v.valueOf();
}
});


map(fmap・flat map)

mapは、与えられた無名関数の第一引数に保持する値を代入して無名関数を実行する構造を持っています。

関数を実行後に、新しいfunctorを生成して中の値として保持します。

例としては、Array.prototype.mapがあります。

[{a: 1}].map(v => ({a: v.a + 5}))


実装例

Object.assign(Monad.prototype, {

map (f) {
return Monad(f(this.v));
}
});

これにより、副作用を隠蔽しているわけです。


bind

ここの実装をすることが中心と言っても過言ではありません。

ここに、モナド則(クライスリ圏における三則)を表現しながら、実装について一緒に考えていけたらと思います。


左随伴則(Left Identity)

関数 f がモナドを返す時、bindを使って f を実行したときと、f をそのまま実行したときで、保持している値は等しくなっていることを要求します。

Monad({a: 1})

.bind(o => Monad({a: o.a + 5}))
.join.a
===
(o => Monad({a: o.a + 5})({a: 1})
.join
.a

が true になる事を求められています。

(実際には同じ関数でなければならないので、関数を定数に保存して証明しますが、分かりやすくするために上記のように記述しました。)

擬似コードで表現すると、

M(x).bind(f) === f(x)

になるということです。


右随伴則(Right Identity)

これはそれほど難しくありません。

bindをするたびに、

Monad({a: 1}).fakeBind(Monad).fakeBind(Monad).fake...

.join.join...

と書いていたのではたまりません。

Monad({a: 1})

.bind(Monad)
.join.a
===
Monad({a: 1}).join.a

になリます。

擬似コードで表現すると、

M(x).bind(M).bind(M).bind... === M(x)

になるということです。


結合則(Associativity)

bindを使って連続して関数を実行した場合には、

関数を合成して実行した場合と、

同じ解になることを要求しています。

この恒等関係は右辺と左辺を入れ替えても成り立ちます。

Monad({a: 1})

.bind(
o => (
Monad({a: o.a + 5})
.bind(o => Monad({a: o.a * 5}))
)
)
.join.a
===
Monad({a: 1})
.bind(o => Monad({a: o.a + 5}))
.bind(o => Monad({a: o.a * 5}))
.join.a

となリますので、擬似コードで表すと、

M(x).bind(

x => fa(x).bind(
y => fb(y).bind(
z => fc(z).bind...
)
)
)
=== M(x).bind(fa).bind(fb).bind(fc).bind...

となるということです。


実装例

実装には、flat mapを利用します。

Object.assign(Monad.prototype, {

bind (f) {
return this.map(f).join;
}
});


恒等モナドの完成

function Monad (v) {

return Object.create(Monad.prototype, {
v: {
configurable: true,
writable: true,
value: v
}
})
}
Object.assign(Monad.prototype, {
get join () {
return this.v.valueOf();
},
map (f) {
return Monad(f(this.v));
},
bind (f) {
return this.map(f).join;
}
});

となります。

他のモナドは、恒等モナドにside effectを付け加えて実装するだけですので、ぜひ取り組んでみてください。

参考:

most

fantasy land

モナドを利用した自作ライブラリ

losand

dsand