2017-05-18 15 views
1

나는 Future [Either [String, A]] 유형을 반환하는 여러 함수를 사용하는 코드를 정리하려고했습니다.Scalaz 7에서 EitherT를 사용할 때 Monad [Future]에 대한 실행 컨텍스트 지정

이러한 기능은 미래를 정점으로보고 나서 내부에서 절충해야하는 문제 때문에 이해하기 쉽도록 구성되지 않습니다. EitherT 모나드 변압기를 사용한 후에는 EitherT를 사용하여 좋아하는 솔루션을 찾았습니다. 둘 중 하나를 추가해야하고 최종 결과를 얻을 때 '실행'을 호출하는 추가 단계가 필요하지 않은 경우에도 이상적이지는 않습니다.

내 솔루션은 만족스럽지 않지만 한 가지 사실은 두 T가 작동하려면 Monad[Future]을 만들어야한다는 것입니다. 실행 컨텍스트가 필요합니다. 그렇게 할 분명한 방법은 없습니다. 내가 한 것은 내 코드의 범위에서 암시 적 실행 컨텍스트를 갖고 동일한 실행 컨텍스트를 전달하는 Future Monad를 작성하여 두 코드가 동일한 실행 컨텍스트를 사용하도록하는 것입니다. 이것은 조금 혼란스럽고 오류가 발생하기 쉬운 것 같습니다.

더 좋은 방법이 있으면 알려주세요. 어렵게해야

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

이 방법은 섞어 실행 컨텍스트 :

/* 

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

나는 대신에 케이스 클래스의 다음과 같은 시도 제안했다. 올바른 방법은 순수 IO 추상화를 사용하는 것입니다. 하나는 실행 컨텍스트가 매핑되거나 플랫 맵핑 될 필요가없는 추상화입니다.