Какой монадный трансформатор использовать?

Я пытаюсь написать функцию проверки ниже, чтобы проверка прекратилась после первой обнаруженной ошибки. Тип возврата 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 может предоставить некоторую помощь.

Другие вопросы по тегам