Scala - monads

6328 ワード

Monads mapおよびflatMapの方法を有するデータ構造は一般的である.実際、there’s a name that describes this class of a data structures together with some algebraic laws that they should have.彼らはMonadsと呼ばれています.では、Monadとは何ですか.What is Monad?
A monad M is a parametric type M[T] with two operations, flatMap and unit, that have to satisfy some laws.
trait M[T] {
  def flatMap[U](f: T => M[U]): M[U]
}
def unit[T](x: T): M[T]

通常、MonadのflatMapbindと呼ばれる.
Examples of Monads
以下にScalaのいくつかのMonadsを挙げます.
  • List is a monad with unit(x) = List(x)
  • Set is monad with unit(x) = Set(x)
  • Option is a monad with unit(x) = Some(x)
  • Generator is a monad with unit(x) = single(x)

  • これらのタイプには同じflatMapメソッドが含まれていますが、unitメソッドはmonadごとに異なる定義が必要です.
    Monads and Map
    各monadについて、flatMapおよびunitによってmapを定義することができる.
    m map f == m flatMap (x => unit(f(x))) == m flatMap (f andThen unit)
    
    andThenメソッドは、関数の組合せを表し、f andThen unitは、まず関数fを実行し、次いで関数unitを実行することを表す.
    Monad Laws
    To qualify as a monad, a type has to satisfy three laws:
  • Associativity:
  • (m flatMap f) flatMap g == m flatMap (x => f(x) flatMap g)
    
  • Left unit
  • unit(x) flatMap f == f(x)
    
  • Right unit
  • m flatMap unit == m
    

    Example of Checking Monad Laws
    次に、Optionmonad lawsに適合していることを示す例を挙げる.まず、OptionflatMapの定義が与えられる.
    abstract class Option[+T] {
      def flatMap[U](f: T => Option[U]): Option[U] = this match {
        case Some(x) => f(x)
        case None => None
      }
    }
    
  • Checking the Left Unit Lawは、まずLeft Unit Law、すなわち証明unit(x) flatMap f == f(x)を証明し、OptionUnitunit(x) = Some(x)と定義されているため、証明Some(x) flatMap f == f(x)である.
  •    Some(x) flatMap f ==
       Some(x) match {
         case Some(x) => f(x)
         case None => None
       } == f(x)
    
  • Checking the Right Unit Lawは、Right Unit Law、すなわちm flatMap unit == m、すなわちm flatMap Some == mを証明する.
  •    m flatMap Some ==
       m match {
         case Some(x) => Some(x)
         None => None
       } == m
    
  • Checking the Associative Law最後に、Associative Law、すなわち(m flatMap f) flatMap g == m flatMap (x => f(x) flatMap g)を証明する.
  •    (m flatMap f) flatMap g ==
       m match { case Some(x) => f(x) case None => None }
         match { case Some(y) => g(y) case None => None } ==
         m match {
         case Some(x) => f(x) match { case Some(y) => g(y) case None => None }
         case None => None match { case Some(y) => g(y) case None => None }
       } ==
       m match {
         case Some(x) => f(x) match { case Some(y) => g(y) case None => None }
         case None => None
       } ==
       m match {
         case Some(x) => f(x) flatMap g
         case None => None
       } == m flatMap (x => f(x) flatMap g)
    

    Significance of the Laws for For-Expressions
  • Associativity says essentially that one can “inline” nested for expressions:
  • for (y 
  • Right unit says:
  •    for (x 
  • Left unit does not have an analogue for for-expressions.

  • Another type: Try
    次のコースで使用されるタイプはTryで、彼の定義は以下の通りです.
    abstract class Try[+T]
    case class Success[T](x: T) extends Try[T]
    case class Failure(ex: Exception) extends Try[Nothing]
    

    ScalaではNothingはすべてのタイプのサブタイプであり、通常、異常が発生した場合、何も返されていないことを表すために使用されます.Tryの役割については、次のように説明されています.
    Try is used to pass results of computations that can fail with an exception between threads and computers.
    すなわち、異常な伝播は、呼び出しスタックではなく、異なるthread、異なるマシン上で伝播することができる.Tryで計算をカプセル化できます.つまり、
    Try(expr) // gives Success(someValue) or Failure(someException)
    

    上記のTryオブジェクトの作成構文をサポートするためには、TryObjectタイプを定義し、applyメソッドを実装する必要がある.applyメソッドは、()メソッド名と同様である.次のようになります.
    object Try {
      def apply[T](expr: => T): Try[T] =
        try Success(expr)
        catch {
          case NonFatal(ex) => Failure(ex)
        }
      }
    }
    

    このうちパラメータ伝達構文expr: => Tcall by nameを表し、つまりパラメータを伝達する際にevaluateの評価を先に行わず、try Success(expr)に入るまでevaluateを行うことができ、これもapply内部で異常を捕捉できる原因である.Option型のように、Tryfor式を使用することもできる.例:
    for {
      x 
    computeXおよびcomputeYが正常に動作し、結果Success(x)および結果Success(y)が得られた場合、式はSuccess(f(x, y))を返す.上記の2つの演算が1つだけエラーが発生した場合、式はFailure(ex)を返します.for式をサポートするためには、Try型のmapメソッドおよびflatMapメソッドを定義する必要がある.次のように定義します.
    abstract class Try[T] {
      def flatMap[U](f: T => Try[U]): Try[U] = this match {
        case Success(x) => try f(x) catch { case NonFatal(ex) => Failure(ex) }
        case fail: Failure => fail
      }
      
      def map[U](f: T => U): Try[U] = this match {
        case Success(x) => Try(f(x))
        case fail: Failure => fail
      }
    }
    

    実際には、mapは、flatMapによって定義することができる.
    t map f == t flatMap (x => Try(f(x))) == t flatMap (f andThen Try)
    

    質問ですが、unit = Tryを定義した後、Tryはmonadではありませんか?答えは:left unit law、つまりTry(expr) flatMap f != f(expr)に合わない.どうしてですか.授業で与えられた解釈は:
    Indeed the left-hand side will never raise a non-fatal exception whereas the right-hand side will raise any exception thrown by expr or f.
    Left unit does not have an analogue for-expressionsの結論によって検証することができ、Tryleft unit law違法であってもfor式を使用することができる.
    Monadという概念は抽象的で、あまり理解しにくいので、今後の授業でMonadの特性を使ってMonadに対する認識を深める必要があります.