1. south37

    No comment

    south37
Changes in body
Source | HTML | Preview
@@ -1,275 +1,275 @@
この記事は [Wantedly Advent Calendar](http://qiita.com/advent-calendar/2015/wantedly) 25日目の記事です。
最終日です!気合いが入りますね!!
## Introduction
今回は Haskell のモナドの話をしたいと思います。
Haskell を学び始めた時、誰もが一度は経験するのが「モナドって何だ?」という疑問です。「Haskell モナド」で検索してみても、圏論を絡めた小難しい説明ばかりが出てきて、よく分からない事が往々にしてあります。
ところが、実は「Haskell におけるモナド」を理解する為に、圏論のモナドを理解する必要はありません。何故なら、「Haskell においてモナドである」為に必要なのは、「たった2つのルールを満たす事」だけだからです!
この記事では、「モナドとは何か」を簡単に説明したいと思います!!
## Haskell におけるモナドとは?
Haskell におけるモナドとは、誤解を恐れずに言えば「いわゆる `flatmap` っぽいものが使える、リストっぽい型」を指します。この、
1. リストっぽい型である
2. flatmap っぽいものが使える
という2つが、先ほど述べた「Haskellにおけるモナド」の条件です。それぞれの条件について詳しく見てみましょう。
#### ミニコラム: flatmapとは
`flatmap` というのは 「コレクション A とコレクションを返す関数 f を受け取って、Aの各要素に f を作用させて得られた結果をフラットなコレクションにして返してくれる」関数です。 言葉だけで説明してもよく分からない感じですが、コードを見たら理解出来ると思います。Ruby や Scala, Swift なんかで使われていて、以下の様な感じになります。
```ruby
irb> [1, 2, 3, 4].flat_map { |e| [e, e * 10] }
=> [1, 10, 2, 20, 3, 30, 4, 40]
```
```Scala
scala> List(1, 2, 3, 4).flatMap(x => List(x, x * 10))
res1: List[Int] = List(1, 10, 2, 20, 3, 30, 4, 40)
```
```Swift
> [1, 2, 3, 4].flatMap { [$0, $0 * 10] }
[1, 10, 2, 20, 3, 30, 4, 40]
```
`flatmap` を Ruby で雑に実装すると、以下の様な感じになります。 `map` してから `flat` するから、`flat_map` という訳ですね。
```ruby
irb> def my_flat_map(arr, &block)
irb* arr.map { |e| block.call(e) }.flatten
irb* end
=> :my_flat_map
irb my_flat_map([1, 2, 3, 4]) { |e| [e, e * 10] }
=> [1, 10, 2, 20, 3, 30, 4, 40]
```
## Haskellにおけるモナドの条件1: 「リストの様に、1つの型引数を受け取ってつくられる型である」
Haskell におけるモナドとは、リスト型やMaybe型、IO型の様な「型」を意味します(より厳密には、`Monad`とは型クラスであり、「`Monad`という型クラスに List型や Maybe型, IO型が属する」 と表現します。型クラスについては後ほど説明します。)
これらの型に共通するのは、どれも「1つの型引数を受け取る」という事です。。。。と、また新しく「型引数」という言葉が出てきたので、一度その意味を説明したいと思います。
### 型引数とは
Haskell には、「型を引数として受け取ってつくられる型」が多数存在しており、その時に渡される型が「型引数」です。
例えば、リストを考えてみましょう。Haskell におけるリストは同じ型の要素だけを含むコンテナ型として定義されており、その型は要素の型を`a`として`[a]`で表されます。ここでは、任意の`a`型に対する`[a]`型を総称して、「リスト型」と呼ぶことにします。
`Int`だけを要素として持つリスト`[1, 2, 3, 4]`は`[Int]`型ですし、文字型`Char`の要素だけを持つリスト`['a', 'b', 'c']`の型は`[Char]`です。これらのリスト型は、`[]`という「型を引数としてとるもの」に対して、`Int`や`Char`のような型を渡したものと捉える事ができます。そのため、リスト型は「1つの型を型引数として受け取ってつくられた型」となります。
ちなみに、`[]`は「型コンストラクタ」と呼ばれます。型を引数として受け取って型をつくりだすので、コンストラクタというわけです。
### モナドは型引数を1つだけ受け取ってつくられる型
リストは型引数を1つだけ受け取ってつくられる型でした。リスト以外のモナド型についても、その型がどういった構造なのかを見てみましょう。
#### Maybe型
Maybe型というのは、「値が存在するかどうか分からない時」に使われる型です。その型は`Maybe a` と表され、`a`が任意の型となります。`a`という型引数を受け取るので、`Maybe`は型コンストラクタとなります。ここでは、`Maybe a`型を総称して、「Maybe型」と呼ぶことにします。
例えば、何かしら失敗する可能性のある計算を行いたい時、失敗した事を明示的に示すために Maybe型が使えます。
例として、「要素とリストを受け取って、要素がリスト中に現れる位置を返す」関数を考えてみましょう。 `elemIndex 2 [1, 2, 3] ` を評価した時、 `1` を返す様な関数`elemIndex`です。この関数の振る舞いを考えた時、問題になるのは「要素がリストの中に存在しなかった」場合です。この時、`elemIndex`は何を返したら良いのでしょうか?
1つの答えとして、「`-1`の様なリストのindexにはなりえない値を返す」という方法もあり得ます。しかし、これは「`-1`は失敗した時の値だ」という前提知識を利用者に強いる事になります。また、似た様な関数なのにライブラリによって失敗の表現の仕方が違うと言った状況も起こり得ます。「失敗は失敗」として明示出来る方が良い場合は多いのです。
こういった状況で活躍するのがMaybe型です。実はHaskellの標準ライブラリにはまさに「リスト中の要素の位置を返す」関数として`Data.List.elemIndex`が用意されており、その関数の返り値は`Maybe Int`型となっています。
`Maybe Int`型の値は`Just (Int型の値)`または`Nothing`です。`Data.List.elemIndex`は要素の発見に成功した時にはその「位置」を`Just`でくるんで(`Just 位置`という値で)返し、失敗した時には`Nothing`を返します。成功や失敗が返り値から自明であり、さらに成功の時には欲しかった情報がその値の中に含まれています。理想的な振る舞いですね。
```haskell
ghci> Data.List.elemIndex 1 [1, 2, 3]
Just 0 -- 1はリストの先頭(位置0)で見つかるので Just 0 が返る。Maybe Int型。
ghci> Data.List.elemIndex 10 [1, 2, 3]
Nothing -- 10はリスト中に存在しないので Nothing が返る。これもMaybe Int型。
```
この様に、任意の型の値を成功時に含むために、Maybe型は1つの型引数`a`を使って`Maybe a`型と表されます。ちなみに興味深いのは`Nothing`で、これは任意の`Maybe a`型に属する値となっており、文脈によってその型は変わります。例えば`Maybe Int`型が期待される場面で`Nothing`が返ってきたら`Nothing`の型は`Maybe Int`ですし、`Maybe Char`型が期待される場面では`Maybe Char`型として振る舞います。見た目は同じでも、実は違うものなのです。
##### 余談: `Just`, `Nothing`などのデータコンストラクタについて
ちなみに余談ですが、慣れないとっつきにくいのが`Just 3`の様な値の表現で、`Just`に違和感を感じるかもしれませんがこれは立派な「値」です。`Just`は値を受け取って値を作る役割を果たしており、「データコンストラクタ」と呼ばれます。作られた値の表現に`Just`という文字が入ってくるのが特徴的ですね。
同じく、`Nothing`もデータコンストラクタですが、これは引数を受け取らないデータコンストラクタとなっています。引数を受け取らないデータコンストラクタとしては、`Bool`型の`True`や`False`があると聞くと、なんとなくイメージがつかめるのではないでしょうか。
#### IO型
もう1つモナド型としてよく知られているのが、IO型です。例によって、任意の型`a`に対して`IO a`型が存在し、これらの`IO a`型を総称してIO型と呼びます。IO型は、「入出力を伴う操作」の型を表現するために使われます。
例えば、標準出力に`Hello, World!`と出力するHaskellプログラムを書きたいとしましょう。実はHaskellには標準出力へ文字列を出力する為に使われる関数`print`が標準で用意されています。この関数は`文字列化が可能な値`を受け取って、`IO ()`型の値を返します。そしてその`IO ()`型の値が持つ「副作用」によって、標準出力へ文字列の出力がなされます。使い方イメージは以下の様な感じです。IO型の値であるからこそ、「標準出力への出力」というIO操作が可能となっています。
```
$ echo 'main = print "Hello, World"' > HelloWorld.hs
$ ghc --make HelloWorld.hs
$ ./HelloWorld
"Hello, World"
```
`()`というのは「unit型」と呼ばれる型で、意味のある値が無い事を示す為に使われます。今回のケースでは「標準出力へ出力する」という副作用自体が重要であった為、`IO ()`型が使われていました。
IO型の値の中でも、意味のある値を持ちたいケースは多々あります。例えば、標準入力から文字を入力として受け取ってプログラムの中で使いたい時、Haskellプログラムでは`getChar`という関数が使われます。この関数は引数を受け取らず、`IO Char`型の値として評価されます。ここで`IO ()`型ではなく`IO Char`型となっているのは、「標準入力から受け取った文字」が`Char`型だからです。つまり、「標準入力から受け取った文字をIOでくるんだ値」を表現する為に`IO Char`型が使われるのです。
IO型には他にも`IO String`などが存在していて、`IO`は1つの型を引数として受け取って型をつくり出す型コンストラクタとなっています。
### 条件1まとめ
ここまで見てきた様に、モナド型はどれも「型引数を1つだけ受け取ってつくられる型」となっています。つまり何かしらの型の値をくるんだ構造となっていて、この「くるまれた値をどう取り出すのか」という問題が次の条件2に繋がってきます。
## Haskellにおけるモナドの条件2: 「他の言語でいうflatMapと同様の役割を果たす、>>=(バインド)という関数が定義されている」
モナド型の値に対しては、`>>=` という関数が必ず定義されています。これは「バインド」と呼ばれる関数で、特にリストに対しては、他の言語でいう `flatMap` と同じ動作をします。
例を見てみましょう。以下のコードでは、1から5までの数字のリストである `list` と、値を受け取ってその2倍の値を2つ並べたリストを作る関数である `makeDoublePair` を定義しています。そして `>>=` を `list` と`makeDoublePair` に対して適用させると、元のリストの各要素が2倍の値で2つずつ並んだリストが作られます。まさに flatMap の様な動作になっているのが分かるでしょうか?
```ghci
Prelude> let list = [1, 2, 3, 4, 5]
Prelude> let makeDoublePair = \x -> [x * 2, x * 2]
Prelude> list >>= makeDoublePair
[2,2,4,4,6,6,8,8,10,10]
```
`>>=` を理解する際、注目するべきなのはその「型」です。`>>=` は `+` などと同じ様に中置演算子(引数の間に書く関数)であり、`list` と `makeDoublePair` の2つを受け取って `[2,2,4,4,6,6,8,8,10,10]` を返す関数となっています。つまり、 `Num t => [t]` と `Num t => t -> [t]` を受け取って、`Num b => [b]` を返す関数となっています。これは、Haskell の REPL である gchi で `:type` コマンドを打って実際に確認してみると分かりやすいかもしれません。(`:type` はその後に書いた値の型を表示してくれるコマンドです。値の後に `::` を印字した後、型が表記されます。)
```ghci
Prelude> :type list
list :: Num t => [t]
Prelude> :type makeDoublePair
makeDoublePair :: Num t => t -> [t]
Prelude> :type (list >>= makeDoublePair)
(list >>= makeDoublePair) :: Num b => [b]
```
`Num t => [t]` 型というのは、`Num` 型クラスに属する任意の型を「 `t` 」と読んだ時に、その `t` のリスト型を表しています。「型クラス」というのは Haskell において型をグループ化する為の仕組みで、数字を表す型は `Num` 型クラスに属しています。Haskell では `1` や `2` のような数値リテラルだけではどの型になるかわからない(Int32, Int64, Integer, Float, Double など様々な型となりうる)為、型が確定するまでは `Num型クラスに属する型のどれか` として扱われます。そして、`:type` コマンドなどで型を印字する際は、`Num 型の任意の型a` という意味で `Num a` という表記を使い、それを `=>` の左側に印字し、 `=>` の右側に `a` を用いて表現した型を記載します。(`a` や `b`, `t` など型を表す変数としては様々なものが使われますが、意味は無いので気にしなくて良いです)
```ghci
Prelude> :type 1
1 :: Num a => a
```
また、`a -> b` という表記は 「`a` 型の値を受け取って `b` 型の値を返す関数の型」を表していて、例えば `makeDoublePair` の型である `Num t => t -> [t]` は 「`Num` 型クラスに属する `t` 型の値を受け取って、`t` 型のリストを返す関数の型」を表しています。
これらの表記を用いて `>>=` の型を記載すると、 `Num t => [t]` と `Num t => t -> [t]` を受け取って、`Num b => [b]` を返す関数であることから `Num t => [t] -> (t -> [t]) -> [t]` 型である事がわかります。(Haskell では、`a` 型と `b` 型の値を受け取って `c` 型の値を返す関数の型を `a -> b -> c` と表現します。これは厳密には 「`a` 型の値を受け取って `b -> c` 型の関数を返す関数の型」なのですが、2つの値を関数に適用するという操作が「1つの値を適用した返り値の関数にもう1つの値を適用する」操作と同一視出来るため、Haskell ではこのような表記を行います。なお、このような「複数引数関数を単一引数関数をネストしたものとみなす」操作は、カリー化と呼ばれます)
では、実際に ghci で `>>=` の型を調べてみましょう。
```ghci
Prelude> :type (>>=)
(>>=) :: Monad m => m a -> (a -> m b) -> m b
```
`Monad` というキーワードが出てきました。これは、`Monad` という型クラスに属する `m a` や `m b` 型が使われる関数という事を意味しています。
ここで、「Haskellにおけるモナドの条件1」の「モナド型は型引数を1つだけ受け取ってつくられる型」という文章を思い出してみましょう。モナド型は、`[]` や `IO` の様な型コンストラクタを持ち、1つの型を引数として受け取る事でつくられます。`>>=` の型に出てきた `Monad m` の `m` はこういった型コンストラクタを表しています。
`>>=` の型である `Monad m => m a -> (a -> m b) -> m b` という表記は多少面喰らうかもしれませんが、`m` をリストの型コンストラクタである `[]` で、 `a` と `b` を `Num t => t` で置き換えると、まさに `Num t => [t] -> (t -> [t]) -> [t]` となっている事がわかると思います。
リスト型に対して `>>=` を適用した際にも、確かに `Monad m => m a -> (a -> m b) -> m b` という型になっている事がわかりました。
### Monad 型クラス
Haskell におけるモナド型とは、 「`Monad` 型クラスに属する型」の事を指します。つまり、Haskell においてモナド型が欲しければ、 `Monad` 型クラスに属する為の条件を満たせば良い事になります。
では、`Monad` 型クラスに属する為の条件とはなんでしょうか?それこそがまさに、`>>=` という関数が定義されている事(それに加えて、`return` という関数が定義されている事)なのです。
「型クラス」は Haskell における型のグループ化の仕組みと述べましたが、グループに属する条件は「指定された特定の関数を定義している事」になります。例えば、順序付け可能な型が属する `Ord` という型クラスは、以下の順序付けに使われる関数の定義を要請します。`<` や `<=`, `>` や `>=` の定義が必要な点が、特徴的です。
```
compare :: a -> a -> Ordering
(<) :: a -> a -> Bool
(<=) :: a -> a -> Bool
(>) :: a -> a -> Bool
(>=) :: a -> a -> Bool
max :: a -> a -> a
min :: a -> a -> a
```
Monad 型クラスを定義する Haskell コードは以下のようになっていて、Monad 型クラスにおいては、この「定義されている必要のある指定された関数」が `>>=` と `return` になります。
```haskell
class Monad m where
(>>=) :: m a -> (a -> m b) -> m b
return :: a -> m a
```
`>>=` はリスト型の例でも見たように、モナド型の値 `m a` と、モナド型の値を返す関数 `a -> m b` を受け取って、モナド型の値 `m b` を返す関数です。その定義の中には、Haskellにおけるモナドの条件1の 「`m` が1引数の型コンストラクタである」という条件が入ってきています。また、この関数を定義することがHaskellにおけるモナドの条件2そのものでした。
`return` はいわゆるデータコンストラクタで、任意の型の値を受け取って、モナド型の値を作り出します。例えば、リスト型のコンテキストで `return 3` を評価すれば `[3]` になりますし、 Maybe 型のコンテキストで `return 3` を評価すれば `Just 3` になります。本当は `return` を定義する事も「Haskellにおけるモナドの条件」に加えるべきなのですが、1引数のデータコンストラクタの定義は大抵サクッと出来ちゃうので省略していました。
また、より厳密に言うと現在は `Functor` や `Applicative` 型クラスに属する事も `Monad` 型クラスに属する為の必要条件となっている(なおかつ Minimal complete definition は `>>=` の定義だけになっている)のですが、 https://wiki.haskell.org/Functor-Applicative-Monad_Proposal を見ると分かるように、 `>>=` と `return` の定義さえ存在していれば、お決まりのコードを書くだけで `Functor` や `Applicative` 型クラスの条件を満たす事が出来ます。
ここまでで、「Haskellにおけるモナドの条件1, 2」を満たせば `Monad` 型クラスに属する事ができる、つまりHaskellにおける「モナド型」となれる事が分かりました。
## モナド型の便利さ
Haskell においてモナド型である事、つまり `Monad` 型クラスである事は、`>>=` が定義されている事とほぼ同義である事が分かりました。では、なぜ「モナド型」がこれほどまでに持て囃されるのでしょうか?`>>=` がそれほど偉いのでしょうか?
実は、`>>=` の凄さはその「便利さ」にあります。例として、`Maybe` 型における `>>=` の利用を考えてみましょう。`Maybe` 型は、計算が成功するか失敗するかが分からない時に返り値を表現する為に使われる型です。失敗の可能性のある計算を連続して行いたい時に、`>>=` は便利な記法となります。
以下のコードでは、文字列から数字の読み取りを `Text.Read.readMaybe` で行い、その計算が成功した時にだけ、得られた数字を使って `Data.List.elemIndex` を評価しています。`Text.Read.readMaybe` は文字列からの読み取りに成功すれば `Just` で包んだ値を、失敗すれば `Nothing` を返す関数であり、`Data.List.elemIndex` は要素とリストを受け取って「要素が何番目に現れたか」を `Just` で包んで返す関数です。どちらも、引数によっては計算に失敗する(`Nothing` を返す可能性のある)関数です。
```
*Main> let list = [1, 2, 3, 4, 5]
*Main> let string = "2"
*Main> (Text.Read.readMaybe string) :: Maybe Int
Just 2
*Main> (Text.Read.readMaybe string) >>= (\x -> Data.List.elemIndex x list)
Just 1
```
`>>=` を使ってこういった計算を繋げると、失敗しない限りはそのまま計算を続けてくれて(Just でくるまれた値を取り出して使ってくれて)、失敗した瞬間に全体の計算結果を `Nothing` にしてくれます。この際、計算と計算の間でわざわざ `Nothing` かどうか確認するためのコードを記述していないのがポイントです。その役割は `>>=` が担ってくれる為、コードはロジックの記述に集中したシンプルなものになります。
実際、Maybe型における `>>=` の定義は以下のようなものになっていて、`Nothing` かどうかの判定は `>>=` が行ってくれています。
```haskell
-Juxt x >>= f = f x
+(Juxt x) >>= f = f x
Nothing >>= f = Nothing
```
IO 型でも同様で、例えば外部から読み取った値を使って計算を行う場合に `>>=` を使います。例として、以下のコードを考えてみましょう。
```haskell
main = getLine >>= (\name -> putStrLn ("Hello, " ++ name))
```
`getLine` を評価した値の型は `IO String` で、外部からの標準入力を `IO` でくるんだ値となっています。ここから値を取り出して計算を行うために、 `>>=` が使われます。
いくつもの入力を読み込むことももちろん可能で、その為には `>>=` をネストさせます。
```haskell
main = getLine >>= (\firstName -> (getLine >>= (\lastName -> putStrLn ("Hello, " ++ firstName ++ lastName))))
```
これで、`firstName` と `lastName` の2回分読み込みを行うことができます。こうやって簡単に IO の数を増やしていけるのも、`>>=` の便利ポイントです。
ちなみに、`>>=` の数が増えるとどんどんコードが読みづらくなっていくので、それを緩和する為に `do構文` と呼ばれる記法がよく使われまます。先ほどのコードを `do構文` を使って書き直すと以下のようになります。
```haskell
main = do
firstName <- getLine
lastName <- getLine
putStrLn ("Hello, " ++ firstName ++ lastName)
```
格段に読みやすくなりました。この様に `do構文` を使えるのも、モナド型の大きな特徴です。
## モナド則って何?
最後に、モナド則についても触れておきたいと思います。モナド則とは、`Monad` 型クラスとして定義したモナド型が「圏論の意味でのモナド」となる為に満たすべきルールであり、また実際に扱いやすい(想定通りの振る舞いをする)型である為に満たしておいて欲しいルールでもあります。
モナド則は以下の3つの等式で表現できます。
```
Left identity: return a >>= f と f a が等価
Right identity: m >>= return と m が等価
Associativity: (m >>= f) >>= g と m >>= (\x -> f x >>= g) が等価
```
これらの意味について興味が湧いた方は [Haskell Wiki の Monad laws](https://wiki.haskell.org/Monad_laws) を読んでみてください。ここではざっくりと、モナド則を満たさない場合になぜ扱いづらいモナド型となってしまうかを例を通して見てみます。
`>>=` は `Monad m => m a -> (a -> m b) -> m b` という型になっていれば良い為、様々な定義がありえます。例えば、`Maybe` 型によく似た `MyMaybe` 型を考えてみます。`MyMaybe` 型の値には `MyNothing` と `MyJust a` が存在し、`Maybe` 型とよく似た動作をするものの、`>>=` が常に `MyNothing` を返すとします。
```
instance Monad MyMaybe where
return a = MyJust a
_ >>= _ = MyNothing
```
`>>=` の型はちゃんと `Monad m => m a -> (a -> m b) -> m b` となっている為、`MyMaybe` 型は `Monad` 型クラスに属しています。
ただし、Monad 則は満たしていません。試しにRight identity を考えてみると、任意の`MyJust a` や `MyNothing` となりうる `m` に対して `m >>= return` は常に `MyNothing` となる為、Right identity は成り立っていない事が分かります。
そして、実際に `MyMaybe` 型における `>>=` は使い物になりません。常に `MyNothing` を返す為、意味のある計算を行う事が出来ないからです。モナド則をちゃんと満たすことで、こう言った状況は避ける事ができます。
## まとめ
Haskell におけるモナドとは、「`Monad` 型クラスに属する型」の事で、その条件は「1つの型引数を持つ型コンストラクタ」を持ち、「`return` と `>>=` が定義されている」事です。また、モナド則と呼ばれるルールを満たす事で、圏論の意味でも正しい「モナド」となり、実用上も扱いやすい型となります。
なにやら小難しく思える「モナド」ですが、これらの簡単なルールを満たすだけでモナドになってしまいます。難しく考えず、モナドと戯れてみましょう!
皆で楽しく、Let's Haskell!!