Which Monad Transformer to use?

2020-03-24 08:38发布

问题:

I am trying to write the validate function below so that the validation stops after the first error encountered. The return type of three is different to the other functions. Which monad transformer do I use in order to make this code compile?

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)

Compilation Error:

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))
     ^

回答1:

There are two general approaches you can take in a situation like this. The first is to make all your methods return the stack you know you'll be working with (in this case EitherT[Future, Int, ?]), or you can have each individual method return the type that most accurately captures its own effects, and then raise the values you get appropriately when you compose them.

The first approach can make usage more syntactically convenient if you know exactly what that usage is going to look like, but the latter approach is more flexible, and in my opinion generally the better choice. In your case it'd look something like this:

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)

And then:

scala> validate("one").run.foreach(println)
-\/(3)

scala> validate("x").run.foreach(println)
-\/(2)

If for some reason you had a plain old Future that you wanted to use in the for-comprehension, you could lift it into the EitherT[Future, String, A] with .liftM[EitherT[?[_], String, ?]].

(Note that this method probably isn't terribly useful, since it'll never succeed (a string can't be equal to "one", "two", and "three" at the same time), but at least the composition works out.)

About how to pick the monad transformer stack more generally: you just turn the types inside out, so that Future[Disjunction[Int, ?]] becomes EitherT[Future, Int, ?], etc. In this case specifically, Future does not have a monad transformer (it's not traversable and it's impossible to implement FutureT without blocking), so you know it has to go on the inside, anyway.



回答2:

There's nothing much to add to Travis' answer (as usual), but in the case you're using this in a Play! application, maybe https://github.com/Kanaka-io/play-monadic-actions can provide some help.