Scalaのモナド

Habréには、Haskellの例を含むモナドに関する記事が多数あります( http://habrahabr.ru/post/183150、http://habrahabr.ru/post/127556 ) Scalaで。 Scala開発者の大部分がオブジェクト指向プログラミングの世界から来ている限り、彼らにとって、最初はモナドとは何か、なぜモナドが必要なのかを理解することは困難です。この記事はそのような開発者向けです。 この記事では、それが何であるかを示し、Optionモナドの使用例を示します。次の記事では、TryとFutureモナドについて説明します。



そのため、モナドはパラメトリックデータ型であり、必然的に2つの操作を実装します:モナド(文献ではunit関数)とflatMap()関数(文献ではbindと呼ばれることもあります)を作成し、いくつかのルールに従います。 これらは、計算リンク戦略を実装するために使用されます。 最も単純なモナドの例を次に示します。



trait Monad[T] { def flatMap[U](f: T => Monad[U]): Monad[U] } def unit[T](x: T): Monad[T]
      
      







flatMap



関数は、入力として、モナド(モナドはコンテナです)にあるデータを受け入れる関数を取り、新しいモナドを返します。 後で示すように、関数は異なるタイプのモナド(TではなくU)を返すことができることに注意してください-これは非常に便利です。



unit



関数に関しては、モナドを作成する責任があり、モナドごとに異なります。 たとえば、ユニット関数。



Option Some(x)





List List(x)





Try Success(x)







各モナドに対して、 map



関数を定義し、 flatMap



unit



組み合わせで表現できます。 例:



 def mapExample() { val monad: Option[Int] = Some(5) assert(monad.map(squareFunction) == monad.flatMap(x => Some(squareFunction(x)))) }
      
      





また、各モナドは3つの法律に従う必要があり、モナド合成が予測可能な方法で機能することを保証する必要があります。 これらの法則をモナドオプションで確認します。



まず、検証に使用する2つの単純な関数を定義します。これらは2乗と増分であり、Optionを返します。これは、flatMapに転送し、さらに合成するために行われます。



  def squareFunction(x: Int): Option[Int] = Some(x * x) def incrementFunction(x: Int): Option[Int] = Some(x + 1)
      
      





最初の法則はLeft unit law



と呼ばれ、次のようになります。



unit(x) flatMap f == f(x)







そして、正の値を持つタイプ(オプションの場合はSome)にflatMap関数を適用し、そこに何らかの関数を渡すと、結果はこの関数を変数に単純に適用するのと同じになると言います。 これは、以下のコードでより良く示されています。



 def leftUnitLaw() { val x = 5 val monad: Option[Int] = Some(x) val result = monad.flatMap(squareFunction) == squareFunction(x) println(result) }
      
      







予想どおり、結果はtrue



になりtrue







2番目の法則はRight unit law



と呼ばれ、次のようになります。



monad flatMap unit == monad







そして、データ(モナドにあるもの)からモナドを作成する関数をflatMapに渡すと、出力で同じモナドが得られると彼は言います。



 def rightUnitLaw() { val x = 5 val monad: Option[Int] = Some(x) val result = monad.flatMap(x => Some(x)) == monad println(result) }
      
      







flatMap関数はモナドを展開し、 x



を取り出して、新しいモナドを構築する関数x => Some(x)



渡します。 flatMap



変数monad



None



に設定されている場合、 flatMap



は単にNone



返すだけで、渡された関数を呼び出さないため、結果はいずれにしてもtrue



になります。



3番目の法則は結合法と呼ばれます:



(monad flatMap f) flatMap g == monad flatMap(x => f(x) flatMap g)







Scalaに書き込む場合:



  def associativityLaw() { val x = 5 val monad: Option[Int] = Some(x) val left = monad flatMap squareFunction flatMap incrementFunction val right = monad flatMap (x => squareFunction(x) flatMap incrementFunction) assert(left == right) }
      
      







そして、この法律のこの順守は、私たちに通常の形で、つまり、次の代わりにfor comprehension



に使用する権利を与えます:



 for (square <- for (x <- monad; sq <- squareFunction(x)) yield sq; result <- incrementFunction(square)) yield result
      
      





私たちは書くことができます:



 for (x <- monad; square <- squareFunction(x); result <- incrementFunction(square)) yield result
      
      







したがって、これらのすべての法則は、 Wikipediaを信じている場合、計算チェーンのロジックをカプセル化できるという事実を示しています。これはまさにモナドの目的です。 これは、 Future



モナドとアクターを適用すると非常に明確に見られますが、これは別の記事のトピックです。 計算の連鎖を示すために、サーバーのポートとホストを計算するための2つの単純な関数を作成し、それらを記述して肯定的なSome



結果を返します。 そして、これらの関数の結果に応じたInetSocketAddress



の作成。



  def findPort(): Option[Int] = Some(22) def findHost(): Option[String] = Some("my.host.com") val address: Option[InetSocketAddress] = for { host <- findHost() port <- findPort() } yield new InetSocketAddress(host, port) println(address)
      
      







このコードの実行結果は次のようになります: Some(my.host.com/82.98.86.171:22)



yield



Option



を返し、それをさらに計算するために使用することに注意してください。 アドレス自体を取得するために、 map



関数を使用して結果を表示します;計算チェーン内の関数のいずれかがNone



返す場合、一般的な結果もNone



ます。



 address.map(add => println("Address : " + add)).getOrElse(println("Error")) // Address : my.host.com/82.98.86.171:22
      
      







モナドを実際に使用するには、まずflatMap



map



が負の入力で実行されないことを覚えておく必要があります( Option



これはNone



)。 これらの機能を使用すると、エラーとの戦いが大幅に簡素化されます。



All Articles