在 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 超过...
我一直在尝试整理一些使用多个函数的代码,这些函数都是 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 超过...