我試圖學習如何在Scala中編寫更多的功能代碼,但是我發現很難不從一元結構中提取值,而是使用map/flatmap等來操縱值。使用單個monad時,這很容易,但是如何擴展這個以適應下面的結構。如何使用所有的monadic結構時,他們嵌套深?
例如什麼是轉換JsResult
內部值的慣用方式?
Option[Future[JsResult[LoginResponse]]]
我試圖學習如何在Scala中編寫更多的功能代碼,但是我發現很難不從一元結構中提取值,而是使用map/flatmap等來操縱值。使用單個monad時,這很容易,但是如何擴展這個以適應下面的結構。如何使用所有的monadic結構時,他們嵌套深?
例如什麼是轉換JsResult
內部值的慣用方式?
Option[Future[JsResult[LoginResponse]]]
不同類型的嵌套monads可能會很棘手,因爲對於理解,要求其中的Monad是相同的類型。你可以這樣做:
大量的嵌套的的
val mappedValue = for (fut <- deepMonad) yield {
for (opt <- fut) yield {
for (bool <- opt) yield {
//some logic
}
}
}
,或者你可以讓其中隱藏它拿走了你一個實用程序。
如果您正在查看項目中常用的特定結構,並且希望堅持使用純粹的Scala,則可以使用類似於下面的方法來製作maps/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可以使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 [email protected](_) => 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: " + _
}
}
如果您可以簡化您的單子到2深,你可以使用Scalaz的股票標準單子變壓器(所建議的評論,例如OptionT)。我已經看到它們在2深處工作得很好,但是我從來沒有在更多嵌套狀態下使用它們。
答案在某種程度上取決於每個級別的含義以及您打算如何處理這些問題。如果不知道更多,我可能會將Future和JsResult變成單一的Future層,然後將'Option'移到Future中,然後(如果我有Cats或Scalaz依賴)轉向結果變成了一個'OptionT [Future,LoginResponse]'。 –
a)http://eed3si9n.com/learning-scalaz/Monad+transformers.html b)http://atnos-org.github.io/eff-cats/ – Reactormonk
@Reactormonk Monad Transformers也許是個主意 - 如果你有關於海報如何使用他們的示例代碼:選項[未來[JsResult [LoginResponse]],這將是偉大的。我已盡了最大的努力作爲答案 - 但很高興看到有更好的東西 –