LoginSignup
31
24

More than 5 years have passed since last update.

[Haskell] 爆速でモナドを理解する

Last updated at Posted at 2018-10-15

何番煎じか分かりませんが、爆速でモナドを説明します。比喩は使いません。
モナドのコンセプトはこの記事の半分ほどで説明し終わるので、残りはオマケです。

*Maybe, State, List, IO など、具体的なモナドの使い方は一切説明していません。
この記事で説明しているのは「モナドとは何か」だけです。
モナドを使いこなせるようになるためには、他の記事をあたって様々なモナドの使い方をひとつずつ覚えていくほかありません。

関数の結合

モナドの説明に入る前に、まず関数結合の演算子を定義しましょう

ghci
GHCi> import Data.Function(flip)
GHCi> let (>.>) = flip (.)

GHCi> :t (>.>) = flip (.)
(>.>) :: (a -> b) -> (b -> c) -> (a -> c)

GHCi> let showSqrt = sqrt >.> show

GHCi> showSqrt 2
"1.4142135623730951"

この演算子は、(.)演算子の引数の順番を逆にしただけです。

さて、この演算子について以下の3つの式が常に成り立ちます(重要

  1. id >.> ff (idの左単位性)
  2. f >.> idf (idの右単位性)
  3. (f >.> g) >.> hf >.> (g >.> h) (結合則)

型コンストラクタ付きの関数の結合

世の中には戻り値が型コンストラクタに包まれていても普通の関数と同じように結合できる場合があります。

そのような関数の結合演算子は既に定義されていて、Control.Monad にある (>=>) という演算子です。

普通の関数結合 型コンストラクタ付き関数結合
演算子 >.> >=>
(a -> b) -> (b -> c) -> (a -> c) (a -> m b) -> (b -> m c) -> (a -> m c)

m が型コンストラクタです。

このような関数結合演算子にも普通の関数結合で見られたような3つの式が成り立っていることが期待されます。

つまり、idに対応するような型コンストラクタ付きの関数 id' :: a -> m a が存在して

  1. id' >=> ff (id'の左単位性)
  2. f >=> id'f (id'の右単位性)
  3. (f >=> g) >=> hf >=> (g >=> h) (結合則)

が成り立っていてほしいということです。

この式が成り立つような m, (>=>), id' の実装をまとめてモナドと呼びます。
ただし、id'は実際には return という名前になっています。

returnという名前を使って上の式を書き直すと以下のようになります。

  1. return >=> ff (returnの左単位性)
  2. f >=> returnf (returnの右単位性)
  3. (f >=> g) >=> hf >=> (g >=> h) (結合則)

これら3つの条件は、まとめてモナド則と呼ばれています。

まとめ: モナドとは

戻り値が型コンストラクタ付きであっても普通の関数と同じように

  • 単位性のある関数が定義でき、
  • 結合則が成り立つ

ような型コンストラクタです。

拍子抜けするほど簡単ですが、これだけです。
なにも省略したり誤魔化したりしていないつもりです。

おまけ: (>>=) ってなんだよ。

以上でモナドの説明は終わりですが、
「(>=>) なんて演算子は知らない。自分が知りたいのは (>>=) だ。」
と思う人も居るでしょう。

>=> >>=
(a -> m b) -> (b -> m c) -> (a -> m c) m a -> (a -> m b) -> m b

実はこの2つの演算子は、片方が決まればもう片方は自動的に実装を決定できます。

つまり、どちらを使っても同じことができるのですが、Haskellでは (>=>) より (>>=) のほうが広く使われています。

その理由を考えるために、以下にこの2つの演算子のそれぞれのメリットをまとめました。

(>>=)のメリット

Haskellで広く(>>=)が使われているのは、コーディングをするうえで便利だからという理由です。

Maybeモナドなどの簡単なモナドを return と (>>=) で実装する場合と、 return と (>=>) で実装する場合について、自分で試してみるといいでしょう。
(>=>)はきっと実装が回りくどくなってしまうかと思います。

また、do 構文との相性も (>>=) のほうが優れています。

(>=>)のメリット

(>=>)のメリットは、上で示したように モナドのコンセプトが理解しやすい という点にあります。
モナド則も(>=>)を使って書くとスッキリしていて、何が言いたいのか一目瞭然です。

一方、(>>=)を使って書かれたモナド則は、初学者の心をへし折るのに十分なほどにクソ醜いです。
以下に(>>=)を使って書かれたモナド則を示します。

  1. return x >>= ff x (returnの左単位性)
  2. m >>= returnm  (returnの右単位性)
  3. (m >>= f) >>= gm >>= (\x -> f x >>= g) (結合則)

ブッサイクな式ですね(暴言)

31
24
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
31
24