Scalaでやってみるモナド再入門 (Scala知らなくてもきっと分かるよ)


今回の目的

今回はモナドの厳密なルールより、なぜ使うのか?どう使えるのか?を簡単なところを数学表現なしでまとめてみるのが目的です。

まだまだモナドを完全に完全理解した! とはいい難いのですが、現在までの理解を書いてみたいとおもいます。

関数型プログラミングに関する熱い話はこちらの記事が参考になります!
関数型言語のウソとホント

関数型を考える上での前提 - プログラミングの作用と副作用

プログラミングの入力と出力は関数の引数と戻り値がメインです。
「作用」とは、その引数に対しての戻り値のことです。

Webアプリケーションサーバでの作用の考え方

HttpのリクエストのデータをHttpのレスポンスデータに変換する処理に過ぎません。

Scalaで表現するとこんな関数定義です。 val func : HttpRequest => HttpResponse
Scalaでは => は関数の定義を示します。funcHttpRequest型のオブジェクトを受け取り、 HttpResponse型のオブジェクトを返す関数です。

Httpリクエストを受け取ったあとの中身に対する細かな処理もformの値を何かしらのオブジェクトにまとめたり、バリデーションは渡されたオブジェクトをtrueやfalseに変換する処理と考えることが出来ます。ほとんどの関数が入力値に対する、ただの変換処理と考える事が出来ます。

副作用

関数の引数と戻り値以外に影響をあたえるものを副作用と呼びます。
ログ出力やファイル入力なども副作用です。
また、最近のプログラミングにおいては変数をイミュータブル(変更不可)にすることも、変数の状況は作ったときに確定することも副作用を抑えて処理を分かりやすくすることや並列処理などにもしやすくする考えの一部に組み込まれているように思えます。

副作用は極力インターフェイスに分解して、関数の処理に考えを持ち込まないように設計する方針がベターとされています。そのために関数型プログラミングでは副作用を閉じ込めたIOモナドなどもあります。

副作用については今回は説明の範囲外とします。

作用と副作用の分解は関数型プログラミングでは前提として考える観点の一つになります。

この前提でモナドのへ考えを少しでも理解していきます。

モナドへのモチベーション

モナドにすると何が良いか?メリットに感じているのは以下の様なところだと思っています。

  • 文脈と関心に分ける事ができること
  • 分けることで自然にモジュラリティが確保されること
  • 関数をつなぎ合わせて表現ができること

文脈と関心を分けると、自然にモジュラリティが確保され、モジュラリティのある関数は文脈に対して自由に繋げ合わせれることが出来ます。

では、文脈と関心とは何なのかを説明していきたいと思います。

文脈と関心

例えば、ある文字列を全て小文字にしたいと考えます。その時に toLowerのような関数があります。
ここでの関心は何らかの文字列であるStringです。

処理の中で以下のようなオブジェクトが渡されました。

List[String]

ここではListは文脈でStringが関心です。
ある文字列を小文字にしたいわけなので、Stringにしか関心がなく、Listはたまたまその形で流れてきた文脈でしかありません。

モナドが説明されるときには何かの箱の中に値が入っている表現をされることがよくありますが、Listが箱でStringが値ですね。ここでは文脈と関心と呼んでいます。

これを処理するときのコードはこのようになりますね?


def toLower = ...

list.map(toLower(_))

では違う文脈のときはどうなるでしょうか?

Option[String]

今度はOption型の文脈で渡された時です。


def toLower = ...

option.map(toLower(_))

同じ関数を適応することが出来ます。
文脈にかかわらず、関心のみに対して関数を作ると、あらゆるStringの値を操作したい時に関数を適応することが出来ます。

これがFutureの場合はどのようになるでしょうか?

Future[String]

すでにわかっていると思いますが、同じです。
非同期処理であっても結果を取得する瞬間と中身の値を操作するものが綺麗に分解されているために、全く同じ関数で利用することが出来ます。

文脈制御の実装こそがモナド

文脈と関心

文脈処理と関心処理の分解されています。
ここで文脈制御しているオブジェクトこそがモナドです。
ListはmapメソッドやflatMapメソッドの実装がされており、Listのイニシャライザなどがモナド則に従って実装されています。Optionなどもそうです。

そして、このあらゆる文脈を結合して利用できるようにルール化されて、文脈制御の実装が入っているものがモナドなのかなと思っています。
こうなっていることで色々なモナドを結合出来るところが利点かと思います。
文脈が綺麗に整理されていることで、forなどのループ文で呼び出すといったことが必要なく、文脈に関数を適応することを書くだけで、モナド内の値に対して適応をすることが出来るようになります!

では、モナドであると言うことを満たすルールはどういうことか?というモナド則を次に説明します。

モナドの定義

モナドである定義は以下の3つです。 Hogeは文脈の型、Tは中身の値の型(ジェネリクス)です。

  • 文脈に包み込むイニシャライザ(コンストラクタ)を持つ。 Hoge[T](x:T)
  • 文脈に関数を適応して返す関数flatMapを持つ。 def flatMap(f:T => Hoge[T]) => Hoge[T] を持つ。
  • この実装がモナド則のルールを守っている

モナド則

モナド則は関数の適応処理に関するルールになります。
直接関数を呼び出して、適応した値とflatMapやコンストラクタを通して適応した値が同じになる事を保証するルールです。

  • Hoge(x).flatMap(f) == f(x) が成り立つ。
  • foo.flatMap { Hoge(_) } == foo が成り立つ。
  • foo.flatMap { f(_).flatMap(g) } == foo.flatMap(f).flatMap(g) が成り立つ。

fとgは値に対して処理を適応する関数。

めちゃくちゃ簡単に書くと

上の規則が守られて居ると
3*(4+5)(3*4) + (3*5) と置き換えることが出来る
3*(9) = 27 = (12) + (15) = 27

つまり
f(a + b)の ようなものがあったときに fa + fb に出来る!
こんな感じのルールを発展させたもの!

(たぶん正確には違うので、雰囲気で理解して欲しい)

モナドになる前の一部処理を持つもの

モナドは色々な条件を達成しなければなりませんが、一部処理を持つものに名前がついています。
その中でよく使う代表的なものを少し紹介します。

関心に処理を実行する ファンクター(functor)

要はmapメソッドの話

関数を受け取ることで持っている値に対して、関数を適応する処理を実行できるということです。
文脈が持つ値を取り出し、引数で渡された関数を適応し、文脈にくるんだまま処理ができます。

文脈も変更したい処理を実行する アプリカティブ ファンクター (applicative Functor)

functorの特殊ケースです。単にアプリカティブとも呼ばれます。(厳密には違うかもだけど)
普通のfunctorでは1つの値しか受け取れませんでしたが、アプリカティブでは2つの値を受け取り処理をすることが出来ます。

例えばOption型の変数aと変数bがいる時に両方を組み合わせて処理が出来るようにしたいときなどです。
現在のOptionの実行では一方がSomeで一方がNoneの時にはNoneが返ってくるようになっています。

2つのListを使った場合には List(1,2,3,4)List(5,6,7) を実行すると、それぞれの中身を一つづつ取り出して、実行される感じになります。結果は
List(1,5),List(1,6),List(1,7),
List(2,5),List(2,6),List(2,7),
List(3,5),List(3,6),List(3,7),
List(4,5),List(4,6),List(4,7)

for-yieldで実装している時のイメージが分かりやすいかもしれません。(flatMapまで行ってしまうとモナドに近いのですけど・・・)

モナドはすべてを満たしている

これらはモナドを満たすためには必要なので、モナドはfunctorでもあるし、applicativeでもあると言えます。
このmapやflatMapの実装はモナド則を満たしていれば実装が出来るので、ここで紹介されたものが満たされているのです。

詳しい実装が知りたい方はScalaZで解説をされている 独習 Scalaz のサイトが非常に参考になります。
その他にScalazやcatsなどのscalaで関数型表現を実現するためのライブラリのコードを読んでみるのも良いかもしれません。

Freeモナド

どんなやつもfunctorを満たしていればモナドに変換できる魔法の箱
たぶん簡単に説明しすぎて意味不明だけど、頑張れ!

継続モナド

モナドを適応するときに、最初に渡された値を計算する度に受け渡せるようにした入れ物
継続モナドについて

リソース管理などで、最初にopenしておいたものを最後にcloseする必要があったり、途中での長さを取り出して利用出来たりするときに使える! (らしい)

まとめ

関数型プログラミングと言われると、モナドが出てきて、モナドってなんだよ!ってなるんです。
調べると圏論とか射とか出てきて、数学勉強すると良いよって出てくるんです。
なんでモナドしたいのか全然わからなかったんです。数学できないし、プログラミングしたいんです。
分かるまでにそんな道を通ってきて、やっと使う意味やポイントが分かった気がするので、雰囲気を書いてみました。

出来るだけ簡単に説明するために、間違っているところがあるかもしれません。
数学的なところも使わず、プログラミングするときにどんな場面で使えそうかを超極限まで簡単にしたつもり。。

本気で勉強したい方はScalaであれば「Scalaで学ぶ関数型デザインプログラミング」 や Haskellであれば「すごいHaskell楽しく学ぼう!」を読むと分かるっぽいです。

あとはこの記事ももう少し突っ込んでモナドについて理解できるかもです。(Haskellだけど)
モナド則がちょっと分かった?

モナドについて完全に理解している自信はあまりないので、いいね、ツッコミ、追加情報を歓迎しています!特に分かりやすいサイトなどあったら教えていただきたい!