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

时间:2017-05-18 05:39:59

标签: scala future scalaz monad-transformers scalaz7

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

由于必须在Future内部达到峰值然后在Either中获取值,因此这些函数不能巧妙地构成一个理解。在使用EitherT monad变换器之后,我找到了一个我喜欢使用EitherT的解决方案,即使必须添加任何一个并且必须有额外的步骤来调用' run'当你得到最终结果并不理想时。

我的解决方案如下,但我不满意的一件事是你需要创建一个Monad[Future]才能使两个工作,这需要一个执行上下文。没有明显的方法可以做到这一点。我所做的是在我的代码范围内有一个隐式执行上下文,并创建一个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)
  }

}

1 个答案:

答案 0 :(得分:1)

我建议尝试以下方法而不是案例类:

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

这样混合执行上下文应该更难。正确的方法是使用纯IO抽象,一个不需要执行上下文映射/平面映射...