2017-05-18 8 views
1

私は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) 
    } 

} 

答えて

1

私はケースクラスの代わりに、次のことをしようと示唆している:

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

実行コンテキストをミックスしにくくする必要があります。この道を。適切な方法は、純粋なIO抽象化を使用することです。実行コンテキストをマップ/フラットマッピングする必要はありません。

関連する問題