我试图学习如何在Scala中编写更多功能代码,但我发现很难不从monadic结构中提取值,而是使用map / flatmap等来操作值。使用单个monad时这很容易,但如何将其缩放以使用如下结构。
例如,在JsResult
内转换值的惯用方法是什么?
Option[Future[JsResult[LoginResponse]]]
答案 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深度工作,但我从未在更嵌套的状态下使用它们。