私はFuture [Either [String、A]]型を返す複数の関数を使用するコードを整理しようとしています。Scalaz 7でEitherTを使用するときのMonad [Future]の実行コンテキストの指定
これらの関数は、未来の中でピークを迎えなければならないという問題と、値を取得するためにEitherの内部で問題が発生するため、きれいには構成されません。 EitherTモナドトランスを使用した後、私は、どちらかを追加する必要があり、最終結果を得るときに '実行'を呼び出す余分なステップを持たなくても、EitherTを使って好きなソリューションを見つけました。理想的ではありません。
私の解決策は以下の通りですが、幸いではない点の1つは、どちらかのTを動作させるために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)
}
}