Locked History Actions

Diff for "scala/monad"

Differences between revisions 13 and 14
Deletions are marked like this. Additions are marked like this.
Line 85: Line 85:
これも簡単。flatMapが動作するとき、fに与えられる引数はxである、ということ。 これも簡単。xを引数としてunitでモナドを作り、そのflatMapが動作するとき、fに与えられる引数はxである、ということ(たぶん)

モナド

※以下は間違いの可能性があるので信用しないように。これは単なる学習メモです。

第一印象

モナドとは、連続して計算を行うパイプラインを簡単に作成するためのものである。古今東西同様の仕組みはいろいろ考えられてきたが、その一種と言える。例えば、

  • unixのパイプ
    入力を受け取ってフィルタリングを行い出力をするコマンドをつなぎ合わせる。

ls -l | grep something |  sort -k 6,6
  • Javaのストリーム
    これも同じ。前のオブジェクトの入力を受け取り、フィルタリングを行う出力するオブジェクトを数珠つなぎにする。

 BufferedReader r = new BufferedReader(new InputStreamReader(new FileInputStream("something")));

形態は異なるけれどもやりたいことはこれと同じ。「任意の計算をつなぎあわせて所望の目的が達成できればとても便利だろう」という悲願の実現であると言える。

モナド則

「モナド」となるオブジェクトは「モナド則」を満たす必要があるという。モナド則とは以下のようなものだ。

  • (1) m flatMap unit ≡ m
  • (2) unit(x) flatMap f ≡ f(x)
  • (3) m flatMap g flatMap f ≡ m flatMap {x => g(x) flatMap f}

関数脳のつくり方 Second Season ~モナドで悟りをひらく~より

Haskellの本にも同じような記述がある(上と合うように順序を並べ替えてある)。

  • 2. m >>= return == m

  • 1. (return x) >>= f == f x

  • 3. (m >>= f) >>= g == m >>= (\x -> f x >>= g)

「ふつうのHaskellプログラミング」(Softbank Creative)より

※ちなみに、先の「関数脳のつくり方」に「それらのメソッド(filter,map,flatMap)があったうえで,「モナド則」を満たす必要があります」とあるが、これは間違いではないだろうか?たしかに書籍「Scalaスケーラブルプログラミング」P438にも「あらゆるモナドは、map、flatMap、filterの3つ、さらに「ユニット」コンストラクターによって特徴づけられる」とあるが、少なくともこれらは厳密なモナドの定義とは無関係のようである。

Listがモナド則を満たすかを確認する

Listがこのとおりに動作するかを確認してみる。まず、unitは単なるコンストラクタ呼び出しであることに注意。

val m = List(1, 2, 3)
def unit = (x: Int) => List(x)
def f = (x: Int) => List(x * 3)
def g = (x: Int) => List(x / 2)

※もちろんすべてのf,gについて確かめることは不可能なので、少なくともf,gの適用順序が変わると結果が変わるようにしとく。

※unitというのは、単なるコンストラクタ呼び出し。

すると、(1)は

m flatMap unit // List(1, 2, 3)になる

(2)は

unit(33) flatMap f // List(99)になる。
f(33)              // List(99)になる。

(3)は

m flatMap g flatMap f           // List(0, 3, 3)になる。
m flatMap {x => g(x) flatMap f}  // List(0, 3, 3)になる

と、とりあえず一種類の関数についてListがモナド則を満たすことは確認できた。

モナド則の意味

さて、モナド則は何を意味しているのだろうか?日本語で説明して欲しいものである。 上のListの例に即して考えてみる。

(1) m flatMap unit ≡ m

これは簡単。Listのそれぞれの要素にコンストラクタを適用し、要素を一つだけ持つListを生成しても、それらはListから取り出され合体されて元通りListになる、ということである。

(2) unit(x) flatMap f ≡ f(x)

これも簡単。xを引数としてunitでモナドを作り、そのflatMapが動作するとき、fに与えられる引数はxである、ということ(たぶん)。

(3) m flatMap g flatMap f ≡ m flatMap {x => g(x) flatMap f}

これは結合則のようなものと思われる。左辺はListに対してflatMap gを適用した結果にflatMap fを適用しているが、 右辺では、個別の要素にgを適用した結果にflatMap fを適用する、という処理をListに適用している。

やはりよくわからないが、しかしこれらのモナド則に反する例を考えてみれば理解を深めることができるのではないか。 しかし、ちょっと考えてみれば、これらに反する例はむしろ「不自然」と言えるので、素直に実装すればモナド則に反することは無いような気がする。どうみても不自然でモナド則に反する例は以下のとおり、

  • unitが引数を無視して無関係なものを返す。
  • flatMapが引数の関数を呼び出した結果を無視したりデタラメに並び替える。

モナドを実装してみる

考えていてもわからないので、とりあえずモナドを実装してみる。 インチキだが、簡単のため、以下のようなコードをでっち上げる。 内部ではListを使っているが、当然ながら外側からはそれはわからない。

case class MyList[T](x: T*) {
  val list: List[T] = x.toList

  def flatMap(f: (T) => MyList[T]): MyList[T] = {
    def fResultToList(x: T): List[T] = {
      val fResult: MyList[T] = f(x)
      fResult.list
    }
    val newList: List[T] = list.flatMap(fResultToList)
    MyList(newList: _*)
  }

  override def toString(): String = {
    list.map(_.toString()).mkString(" ")
  }
}

これを以下のように呼び出す。

    val m = new MyList(1, 2, 3)
    println(m)

    def unit = (x: Int) => MyList(x)
    def f = (x: Int) => MyList(x * 3)
    def g = (x: Int) => MyList(x / 2)

    println(m flatMap unit)
    println(unit(33) flatMap f)
    println(f(33))
    println(m flatMap g flatMap f)
    println(m flatMap {x => g(x) flatMap f})

結果は以下のようになり、リストモナドの完成である(たぶん)。

1 2 3
1 2 3
99
99
0 3 3
0 3 3

ここまでの「推測」が間違っていなければ、モナドとは非常に単純なものであることがわかる。 おそらく、この単純なものを使って、難しいことをやろうとするから難しくなってしまうのだろう。 モナドを説明するサイトがいくつもあるが、例が難しすぎて(あるいはややこしすぎて)途中で面倒になってしまう。

参考