Fight the Future

Java言語とJVM、そしてJavaエコシステム全般にまつわること

プレゼン、ボランティアコーチします!

勉強会でスピーカーをやりたいけど、プレゼンが初めて、苦手という方に無償でコーチします!資料レビューや録画リハへアドバイスします。Twitter@jyukutyoまでメンションでもDMでも。

私はデブサミやJJUG CCCなど200人規模で経験ありです。海外も短いながら経験あり。デブサミ2017では公募スピーカー1位でした!

モナドについて調べていく(10)

One Div Zero: Monads are Elephants Part 1の翻訳続き。
ヤバい!ここは超わかりやすい!
モナドで困ってる人はゼヒ!おすすめ!
bindとかflatMapの役割がわかってきた!

Monads are Combinable(モナドは結合可能だ)

Now let's say we have a configuration library that let's us fetch parameters.

さあパラメータをフェッチする設定ライブラリについて話しましょう。

For any parameter we'll get back an Option[String] - in other words we may or may not get a string depending on whether the parameter is defined.

あらゆるパラメータのためにOption[String]に戻ります。言い換えると、文字列を取得できるかどうかはパラメータが定義されているかどうかに依存します。

Let's say we also have a function, stringToInt, which takes a String and returns Some[Int] if the string is parseable as an integer or None if it's not. If we try to combine them using map we run into trouble.

関数stringToIntもあります。Stringを引数に取りSome[Int]を返す関数です。
もし数値に変換できるなら文字列を、そうでないならNoneを返します。
これをmapと組み合わせてみると、とたんに混乱に陥ります。

val opString : Option[String] = config fetchParam "MaxThreads"
def stringToInt(string:String) : Option[Int] = ...
val result = opString map stringToInt


Unfortunately, since we started with an Option and mapped its contained element with a function that results in another Option, the variable "result" is now an Option that contains an Option, ie result is an Option[Option[Int]].

不幸なことに、Optionから始まり、そのコンテナにある要素を他のOptionに変換する関数でマップするので、変数「result」は今Optionを含んだOptionです。すなわちOption[Option[Int]]です。


That's probably not terribly useful in most cases.

これはおそらくほとんどの場合ひどく使えないものです。


To motivate a solution, imagine if instead of Option we'd used List and ended up with List[List[Int]]] - in other words a list containing some number of lists.

解決方法に興味を起こすために、想像してみましょう。もしOptionの代わりにリストを使ってList[List[Int]]]で終わったなら。
言い換えると、いくつかのリストを含んだリストならどうでしょう。


Given that, we just need "flatten" - a function which takes a list of lists (List[List[A]]) and returns a single list (List[A]) by concatenating everything together.

そうすると「flatten」が必要です。これはリストのリスト(List[List[A]])を引数に取り、すべてを一緒につなぎ合わせて単なるリスト(List[A])にして返します。


A flatten function for Option[Option[A]] works a bit differently.

Option[Option[A]]のflatten関数は少し異なります。

def flatten[A](outer:Option[Option[A]]) : Option[A] = 
   outer match {
     case None => None 
     case Some(inner) => inner 
   }

If the outer option is None, then result is None. Otherwise the result is the inner Option.

もしOption型のouterがNoneなら結果もNoneです。そうでなければ結果はOptionに入っている値です。

These two flatten functions have similar signatures: they take an M[M[A]] and turn it into an M[A]. But the way they do it is quite different.

これら2つのflatten関数はシグネチャが似ています。M[M[A]]を引数に取りM[A]に変換します。
しかしその方法はまったく異なります。


Other monads would have their own ways of doing flatten - possibly quite sophisticated ways.

他のモナドは独自の方法でflattenを実現します。おそらく完全に洗練された方法で。


This possible sophistication is why explanations of monads will often use "join" instead of "flatten." "Join" neatly indicates that some aspect of the outer monad may be combined (joined) with some aspect of the inner monad.

これを洗練したものとして、「flatten」の代わりに「join」を使ってよくモナドを説明しています。
「join」は外部のモナドにおけるある面が内部のモナドのある面とともに組み合わされ(結合され)ているということをきちんと示しています。


I'll stick with "flatten," though, because it fits with our container analogy.

しかしながら私は「flatten」でやります。コンテナのアナロジーにフィットするからです。


Now, Scala does not require you to write flatten explicitly. But it does require that each monad have a method called flatMap. What's flatMap? It's exactly what it sounds like: doing a map and then flattening the result.

さて、Scalaは明快にflattenを記述する必要はありません。
しかしflatMapと呼ばれるメソッドを各モナドを持つようにする必要があります。
flatMapとは何でしょう?文字のとおりです。
mapし、その結果をflattenします。

class M[A] {
  private def flatten[B](x:M[M[B]]) : M[B] = ...
  def map[B](f: A => B) : M[B] = ...
  def flatMap[B](f: A => M[B]) : M[B] = flatten(map(f))
}

With that, we can revisit our problematic code...

これを頭に入れて、先ほどの問題あるコードに戻りましょう。

val opString : Option[String] = config fetchParam "MaxThreads"
def stringToInt(string:String) : Option[Int] = ...
val result = opString flatMap stringToInt

Because of flatMap we end up with "result" being an Option[Int]. If we wanted, we could take result and flatMap it with a function from Int to Option[Foo]. And then we could faltMap that with a function from Foo to Option[Bar], etc.

flatMapのために変数resultはOption[Int]型となります。もし望むならresultを引数にしてIntをOption[Foo]にする関数でflatMapさせることもできます。
それからFoo型をOption[Bar]にする関数でflatMapさせることもできます。などなど。


If you're keeping score, many papers on monads use the word "bind" instead of "flatMap" and Haskell uses the ">>=" operator. It's all the same concept.

気をつけることとして、モナドに関する多くの論文が「flatMap」の代わりに「bind」という単語を使っています。
Haskellでは「>>=」という記号を使っています。すべて同じコンセプトです。