在 Scalaz 7 中使用 EitherT 时为 Monad[Future] 指定执行上下文

Specifying an execution context for Monad[Future] when using EitherT in Scalaz 7

我一直在尝试整理一些使用多个函数的代码,这些函数都是 return 类型 Future[Either[String, A]]。

由于必须在 Future 中达到峰值然后在 Either 中获取值的问题,这些函数不能整齐地组合在 for comprehension 中。在使用 EitherT monad 转换器之后,我找到了一个我喜欢使用 EitherT 的解决方案,尽管必须添加 eitherT 并且在获得最终结果时必须执行调用 'run' 的额外步骤并不理想。

我的解决方案如下,但有一件事我不满意,您需要创建一个 Monad[Future] 才能使 eitherT 工作,这需要一个执行上下文。没有明显的方法可以做到这一点。我所做的是在我的代码范围内有一个隐式执行上下文,并创建一个 Future Monad,我将相同的执行上下文传递给它,以便两段代码使用相同的代码。这看起来有点乱,容易出错。

如果有更好的方法请告诉我。

/*

Example of EitherT in ScalaZ

val scalaZVersion = "7.2.8"
 "org.scalaz" %% "scalaz-core" % scalaZVersion,
  "org.scalaz" %% "scalaz-effect" % scalaZVersion,
*/

import java.util.concurrent.Executors

import scala.concurrent.duration._
import org.scalatest._
import scala.concurrent.{Await, ExecutionContext, Future}
import scalaz.{-\/, Monad, \/, \/-}
import scalaz.EitherT.eitherT

object MonadFutureUtil {

  // a Future Monad with a specific instance of an EC
  case class MonadWithExecutionContext()(implicit ec : ExecutionContext) extends Monad[Future] {
    def point[A](a: => A): Future[A] = Future(a)
    def bind[A, B](fa: Future[A])(f: (A) => Future[B]): Future[B] = fa flatMap f
  }

}

class TestFutureUtil extends FlatSpec with Matchers with OptionValues with Inside with Inspectors {

  implicit val ec = new ExecutionContext {

    implicit val threadPool = Executors.newFixedThreadPool(8)

    def execute(runnable: Runnable) {
      threadPool.submit(runnable)
    }

    def reportFailure(t: Throwable): Unit = {
      println(s"oh no! ${t.getMessage}")

    }
  }

  implicit val monadicEC = MonadFutureUtil.MonadWithExecutionContext()(ec)

  // halves the input if it is even else fails
  def dummyFunction1(n: Int)(implicit ec : ExecutionContext) : Future[\/[String, Int]] = {
    Future.successful(
      if(n % 2 == 0)
        \/-(n / 2)
      else
        -\/("An odd number")
    )
  }

  // appends a suffix to the input after converting to a string
  // it doesn't like numbers divisible by 3 and 7 though
  def dummyFunction2(n: Int)(implicit ec : ExecutionContext) : Future[\/[String, String]] = {
    Future.successful(
      if(n % 3 != 0 && n % 7 != 0)
        \/-(n.toString + " lah!")
      else
        -\/(s"I don't like the number $n")
    )
  }

  "EitherFuture" should "add the results of two dummyFunction1 calls" in {

      val r = for (
        rb1 <- eitherT(dummyFunction1(8));
        rb2 <- eitherT(dummyFunction1(12))

      ) yield (rb1 + rb2)

      r.run.map {
        _ shouldBe \/-(11)
      }
  }

  it should "handle functions with different type" in {

    val r = for (
      rb1 <- eitherT(dummyFunction1(14));
      rb2 <- eitherT(dummyFunction1(12));
      rb3 <- eitherT(dummyFunction2(rb2 + rb1))

    ) yield rb3

    val r2 = Await.result(r.run.map {
      case \/-(s) =>
        (s == "13 lah!")
      case -\/(e) =>
        false
    }, 5 seconds)

    assert(r2)
  }

  it should "doesn't like divisible by 7" in {

    val r = for (
      rb1 <- eitherT(dummyFunction1(14));
      rb2 <- eitherT(dummyFunction1(14));
      rb3 <- eitherT(dummyFunction2(rb1 + rb2))

    ) yield rb3

    val r2 = Await.result(r.run.map {
      case \/-(s) =>
        false
      case -\/(e) =>
        true
    }, 5 seconds)

    assert(r2)
  }

}

我建议尝试以下而不是案例 class:

implicit def MWEC(implicit ec: ExecutionContext): Monad[Future] = ???

这样应该更难混淆执行上下文。正确的方法是使用纯 IO 抽象,不需要执行上下文 mapped/flatmapped 超过...