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関数を定義し、 flatMapunit組み合わせで表現できます。 例:

 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)yieldOptionを返し、それをさらに計算するために使用することに注意してください。 アドレス自体を取得するために、 map関数を使用して結果を表示します;計算チェーン内の関数のいずれかがNone返す場合、一般的な結果もNoneます。

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


モナドを実際に使用するには、まずflatMapmapが負の入力で実行されないことを覚えておく必要があります( OptionこれはNone )。 これらの機能を使用すると、エラーとの戦いが大幅に簡素化されます。

Source: https://habr.com/ru/post/J209510/


All Articles