Какой монадный трансформатор использовать?
Я пытаюсь написать функцию проверки ниже, чтобы проверка прекратилась после первой обнаруженной ошибки. Тип возврата three
отличается от других функций. Какой монадный преобразователь я использую для компиляции этого кода?
import scalaz._
import Scalaz._
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
def one(a : String): Disjunction[Int, String] =
a == "one" match {
case true => \/-("one")
case false => -\/(2)
}
def two(a : String): Disjunction[Int, String] =
a == "two" match {
case true => \/-("two")
case false => -\/(3)
}
def three(a : String): Future[Disjunction[Int, String]] =
Future (a == "three") map {
case true => \/-("three")
case false => -\/(4)
}
def validate(a : String) = for {
e1 <- one(a)
e2 <- two(a)
e3 <- EitherT(three(a))
} yield (e1 |+| e2 |+| e3)
Ошибка компиляции:
Error:(27, 7) type mismatch;
found : scalaz.EitherT[scala.concurrent.Future,Int,String]
required: scalaz.\/[?,?]
e3 <- EitherT(three(a))
^
Error:(66, 7) type mismatch;
found : scalaz.EitherT[scala.concurrent.Future,Int,String]
required: scalaz.\/[?,?]
e3 <- EitherT(three(a))
^
2 ответа
Есть два основных подхода, которые вы можете использовать в такой ситуации. Первый - заставить все ваши методы возвращать стек, с которым вы будете работать (в данном случае EitherT[Future, Int, ?]
), или вы можете сделать так, чтобы каждый отдельный метод возвращал тип, наиболее точно отражающий его собственные эффекты, а затем увеличивал значения, которые вы получаете соответствующим образом при их создании.
Первый подход может сделать использование более синтаксически удобным, если вы точно знаете, как будет выглядеть это использование, но последний подход более гибкий, и, на мой взгляд, в целом лучший выбор. В вашем случае это будет выглядеть примерно так:
import scalaz._, Scalaz._
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
def one(a: String): Disjunction[Int, String] = (a == "one").either("one").or(2)
def two(a: String): Disjunction[Int, String] = (a == "two").either("two").or(3)
def three(a: String): EitherT[Future, Int, String] = EitherT(
Future(a == "three").map(_.either("three").or(4))
)
def validate(a: String) = for {
e1 <- EitherT.fromDisjunction[Future](one(a))
e2 <- EitherT.fromDisjunction[Future](two(a))
e3 <- three(a)
} yield (e1 |+| e2 |+| e3)
А потом:
scala> validate("one").run.foreach(println)
-\/(3)
scala> validate("x").run.foreach(println)
-\/(2)
Если по какой-то причине у вас был простой старый Future
что вы хотели использовать в for
понимание, вы можете поднять его в EitherT[Future, String, A]
с .liftM[EitherT[?[_], String, ?]]
,
(Обратите внимание, что этот метод, вероятно, не очень полезен, так как он никогда не будет успешным (строка не может быть равна "one"
, "two"
, а также "three"
в то же время), но по крайней мере композиция работает.)
О том, как выбрать монадный стек трансформаторов в более общем смысле: вы просто выворачиваете типы наизнанку, Future[Disjunction[Int, ?]]
становится EitherT[Future, Int, ?]
и т. д. В данном случае конкретно Future
не имеет монадного трансформатора (это не проходимо и невозможно реализовать FutureT
без блокировки), так что вы знаете, это должно идти внутри, в любом случае.
Ничего особенного добавить к ответу Трэвиса (как обычно), но в случае, если вы используете это в игре! приложение, может быть https://github.com/Kanaka-io/play-monadic-actions может предоставить некоторую помощь.