Locked History Actions

Diff for "scala/monad"

Differences between revisions 9 and 10
Deletions are marked like this. Additions are marked like this.
Line 76: Line 76:
==== m flatMap unit ≡ m ==== ==== (1) m flatMap unit ≡ m ====
Line 78: Line 78:
これは簡単。Listのそれぞれの要素にコンストラクタを適用し、要素を一つだけ持つListを生成しても、それらList合体されて元通りになる、ということである。 これは簡単。Listのそれぞれの要素にコンストラクタを適用し、要素を一つだけ持つListを生成しても、それらListから取り出され合体されて元通りListになる、ということである。
Line 80: Line 80:
==== unit(x) flatMap f ≡ f(x) ==== ==== (2) unit(x) flatMap f ≡ f(x) ====
Line 84: Line 84:
==== m flatMap g flatMap f ≡ m flatMap {x => g(x) flatMap f} ==== ==== (3) m flatMap g flatMap f ≡ m flatMap {x => g(x) flatMap f} ====
Line 89: Line 89:
うーむ、やっぱりわからない。 やはりよくわからないが、しかしこれらのモナド則に反する例を考えてみれば理解を深めることができるのではないか。
しかし、ちょっと考えてみれば、これらに反する例はむしろ「不自然」と言えるので、素直に実装すればモナド則に反することは無いような気がする。

モナド

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

第一印象

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

  • 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)より

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

Listがこのとおりに動作するかを確認してみる。まず、flatMapはそもままflatMapメソッドだが、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)

これも簡単。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に適用している。

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

モナドを実装してみる

考えていてもわからないので、とりあえずモナドを実装してみる。 インチキだが、簡単のため、以下のようなコードをでっち上げる。 内部では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

参考