如何在嵌套深度时使用所有monadic结构?

时间:2016-10-01 22:01:10

标签: scala monads

我试图学习如何在Scala中编写更多功能代码,但我发现很难不从monadic结构中提取值,而是使用map / flatmap等来操作值。使用单个monad时这很容易,但如何将其缩放以使用如下结构。

例如,在JsResult内转换值的惯用方法是什么?

Option[Future[JsResult[LoginResponse]]]

1 个答案:

答案 0 :(得分:0)

不同类型的嵌套monad可能很棘手,因为对于理解要求其中的Monads是相同的类型。你可以这样做:

很多嵌套的

val mappedValue = for (fut <- deepMonad) yield {
  for (opt <- fut) yield { 
    for (bool <- opt) yield {
      //some logic
    }       
  }
}

或者你可以制作一个隐藏它的工具。

如果您正在查看项目中常用的特定结构,并且想要坚持使用纯Scala,那么您可以使用下面的内容来制作地图/ foreach。

E.g。

object MyUtils {

  implicit class MyWrapper[A](deepMonad: Option[Future[Option[A]]]) {

    def fmap[B](f: A => B) = {
      for (fut: Future[Option[A]] <- deepMonad) yield {
        for (opt: Option[A] <- fut) yield {
          for (b: A <- opt) yield {
            f(b)
          }
        }
      }
    }

    def myForeach[U](f: A => U): Unit = {
      for (future <- deepMonad) {
        for (opt <- future) {
          for (b <- opt) {
            f(b)
          }
        }
      }
    }
  }

}

object Test extends App {

  import MyUtils._

  val deepMonadExample:Option[Future[Option[Boolean]]] = Some(Future.successful(Some(true)))

  val x: Option[Future[Option[String]]] = deepMonadExample.fmap { 
    case v:Boolean => "Result: "+v 
  }
  x.myForeach{ v => println(v) }

}

如果您愿意使用Scalaz,可以使用Monad类创建更通用的util。 Scalaz中有一些预先构建的含义,这将使Option,Future和其他工具开箱即用。但是像JsResult这样的类没有scalaz Monad实例,所以你需要创建一个。

E.g。

import play.api.libs.json.{JsSuccess, JsError, JsResult}

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
import scalaz.Monad

//this creates the fmap method
object HandleDeepMonads {

  import scala.language.higherKinds
  import scalaz.Monad
  import scalaz.Scalaz._

  implicit class ThreeMonadMap[M[_] : Monad, M2[_] : Monad, M3[_] : Monad, A](v: M[M2[M3[A]]]) {
    def fmap[B](f: A => B): M[M2[M3[B]]] = {
      for (a <- v) yield
        for (b <- a) yield
          for (c <- b) yield f(c)
    }
  }

}

//Since JsResult has no native Monad in scalaz - you can make your own one
object MyCustomMonads {

  implicit object JsResultMonad extends Monad[JsResult] {
    def point[A](a: => A): JsResult[A] = JsSuccess(a)

    def bind[A, B](fa: JsResult[A])(f: A => JsResult[B]): JsResult[B] = fa match {
      case JsSuccess(v, _) => f(v)
      case e@JsError(_) => e
    }
  }

}

object Test extends App {

  import HandleDeepMonads._
  import MyCustomMonads._
  import scala.language.higherKinds
  import scalaz.Scalaz._

  val deepMonadExample: Option[Future[JsResult[String]]] = Some(Future.successful(JsSuccess("Hello")))
  val deepMonadExample2: Option[Future[JsResult[Boolean]]] = Some(Future.successful(JsError(Nil)))
  val deepMonadExample3: Option[Future[Option[Boolean]]] = Some(Future.successful(Some(true)))
  val deepMonadExample4: Option[Future[JsResult[Boolean]]] = None

  // Some(successful(JsSuccess("Result: true")))
  val x = deepMonadExample.fmap {
    "Result: " + _
  }
  // Some(successful(JsError()))
  val x3 = deepMonadExample3.fmap {
    "Result: " + _
  }
  // Some(successful(Some("Result: Hello")))
  val x2 = deepMonadExample2.fmap {
    "Result: " + _
  }
  // None
  val x4 = deepMonadExample4.fmap {
    "Result: " + _
  }
}

如果您可以将monad简化为2深,则可以使用Scalaz的股票标准monad变换器(如评论中所建议的,例如OptionT)。我已经看到他们在2深度工作,但我从未在更嵌套的状态下使用它们。