2016-06-13 5 views
6

무료 모나드를 사용하는 ETL 프로세스에 간단한 언어를 구현했습니다. 데이터 가져 오기 및 저장 모두에 대해 입력 및 출력으로 List을 사용하면 모든 것이 잘 동작합니다. 그러나 예상대로 나는 Future[List]무료 모나드 사용 방법 [M [_]]

case class Fetch(offset: Int, amount: Int) extends Ops[Future[List[Record]]] 
case class Store(recs: List[Record]) extends Ops[Future[List[Response]]] 

def fetch(offset: Int, amount: Int): OpsF[Future[List[Record]]] = 
    liftF[Ops, Future[List[Record]]](Fetch(offset, amount)) 
def store(recs: List[Record]): OpsF[Future[List[Response]]] = 
    liftF[Ops, Future[List[Response]]](Store(recs)) 

// explicit types in case I am misunderstanding more than I think 
def simpleEtl(offset: Int, amount: Int): Free[Ops, Future[List[Response]]] = 
fetch(offset, amount).flatMap { rf: Future[List[Record]] => 
    val getResponses: OpsF[Future[List[Response]]] = rf map { r: List[Record] => 
    store(r) 
    } 
    getResponses 
} 

작동하지 List

case class Fetch(offset: Int, amount: Int) extends Ops[List[Record]] 
case class Store(recs: List[Record]) extends Ops[List[Response]] 

def fetch(offset: Int, amount: Int): OpsF[List[Record]] = 
    liftF[Ops, List[Record]](Fetch(offset, amount)) 
def store(recs: List[Record]): OpsF[List[Response]] = 
    liftF[Ops, List[Response]](Store(recs)) 

def simpleEtl(offset: Int, amount: Int): Free[Ops, List[Response]] = 
    fetch(offset, amount).flatMap(r => store(r)) 

작업을 비동기 라이브러리와 Future[List]

일반적인 수입과 작업과 정의

import scala.concurrent.Future 
import scala.concurrent.ExecutionContext.Implicits.global 
import cats.free.Free 
import cats.free.Free._ 

sealed trait Ops[A] 
type OpsF[A] = Free[Ops, A] 

를 사용하고는 에서 반환 된 유형 flatMap/map 잘못이다 - 나는 OpsF[Future]을 받고 있지 않다 그러나 Future[OpsF]

Error:(34, 60) type mismatch; 
found : scala.concurrent.Future[OpsF[scala.concurrent.Future[List[Response]]]] 
(which expands to) scala.concurrent.Future[cats.free.Free[Ops,scala.concurrent.Future[List[String]]]] 
required: OpsF[scala.concurrent.Future[List[Response]]] 
(which expands to) cats.free.Free[Ops,scala.concurrent.Future[List[String]]] 
    val getResponses: OpsF[Future[List[Response]]] = rf map { r: List[Record] => 

내 현재 해결 방법은 storeFuture[List[Record]]을 적용하고 Future를 통해 통역지도를시키는 것입니다,하지만 어색한 느낌.

문제는 List과 관련이 없습니다. 예 : Option도 유용합니다.

내가 잘못 했습니까? 거기에 일종의 모나드 변압기가 있나요?

+0

첫 번째 모습처럼 보인다에서이, 모나드 변압기의 일반적인 패턴처럼 보인다 하스켈은 어떻게 든'FreeT'를 가지고 있지만 스칼라즈 나 고양이에서는 찾을 수 없습니다. –

+3

scalaz는 [7.2.0]부터'FreeT'를 가지고 있습니다 (https://oss.sonatype.org/service/local/repositories/releases/archive/org/scalaz/scalaz_2.11/7.2.0/scalaz_2.11-7.2). .0-javadoc.jar /!/index.html # scalaz.FreeT). –

+1

곧 47 레벨의 도서관을 http://47deg.github.io/fetch/로 명명 할 수 있습니까? 곧 typelevel 인큐베이터가 될 것입니까? 47 도의 운동을하지는 않지만, 이미하고 싶은 일에 대한 해결책이 이미있는 것처럼 보입니다. – wheaties

답변

7

추상 데이터 유형 Ops에 대수가을 가져오고 저장 여러 Record들에 정의합니다. 그것은 두 가지 연산을 설명하지만 대수학이 수행해야 할 유일한 연산이기도합니다. 작업이 실제로 실행되는 방식은 모두 FetchStore으로 중요하지 않습니다. 예상되는 유일한 유용한 점은 각각 List[Record]List[Response]입니다.

예상 결과 형식을 FetchStore으로 설정하면 Future[List[Record]]]이되므로이 대수를 해석하는 방법이 제한됩니다. 어쩌면 테스트에서 웹 서비스 나 데이터베이스에 비동기 적으로 연결하고 Map[Int, Result] 또는 Vector[Result]으로 테스트하기를 원하지만 이제는 Future을 반환해야 테스트가 더 복잡해 질 수 있습니다.

그러나 ETL[Future[List[Record]]]이 필요 없다고 말하면 질문을 해결할 수 없습니다. 비동기 라이브러리를 사용하고 있으며 Future을 돌려 주려고합니다.

import scala.concurrent.Future 
import scala.concurrent.ExecutionContext.Implicits.global 
import cats.implicits._ 
import cats.free.Free 

type Record = String 
type Response = String 

sealed trait EtlOp[T] 
case class Fetch(offset: Int, amount: Int) extends EtlOp[List[Record]] 
case class Store(recs: List[Record]) extends EtlOp[List[Response]] 

type ETL[A] = Free[EtlOp, A] 

def fetch(offset: Int, amount: Int): ETL[List[Record]] = 
    Free.liftF(Fetch(offset, amount)) 
def store(recs: List[Record]): ETL[List[Response]] = 
    Free.liftF(Store(recs)) 

def fetchStore(offset: Int, amount: Int): ETL[List[Response]] = 
    fetch(offset, amount).flatMap(store) 

을하지만 지금 우리는 여전히 Future의이 없다 :

첫 번째 구현 시작?즉 우리의 통역의 일이다 :

import cats.~> 

val interpretFutureDumb: EtlOp ~> Future = new (EtlOp ~> Future) { 
    def apply[A](op: EtlOp[A]): Future[A] = op match { 
    case Store(records) => 
     Future.successful(records.map(rec => s"Resp($rec)")) 
     // store in DB, send to webservice, ... 
    case Fetch(offset, amount) => 
     Future.successful(List.fill(amount)(offset.toString)) 
     // get from DB, from webservice, ... 
    } 
} 

이 통역으로 우리가 얻을 수 있습니다 (물론 당신이 더 유용 뭔가 Future.successful(...)를 대체 할의) 우리의 Future[List[Response]] :

val responses: Future[List[Response]] = 
    fetchStore(1, 5).foldMap(interpretFutureDumb) 

val records: Future[List[Record]] = 
    fetch(2, 4).foldMap(interpretFutureDumb) 

responses.foreach(println) 
// List(Resp(1), Resp(1), Resp(1), Resp(1), Resp(1)) 
records.foreach(println) 
// List(2, 2, 2, 2) 

그러나 우리는 여전히 다른를 만들 수 있습니다 반환하지 않습니다 통역 Future :

import scala.collection.mutable.ListBuffer 
import cats.Id 

val interpretSync: EtlOp ~> Id = new (EtlOp ~> Id) { 
    val records: ListBuffer[Record] = ListBuffer() 
    def apply[A](op: EtlOp[A]): Id[A] = op match { 
    case Store(recs) => 
     records ++= recs 
     records.toList 
    case Fetch(offset, amount) => 
     records.drop(offset).take(amount).toList 
    } 
} 

val etlResponse: ETL[List[Response]] = 
    for { 
    _  <- store(List("a", "b", "c", "d")) 
    records <- fetch(1, 2) 
    resp <- store(records) 
    } yield resp 

val responses2: List[Response] = etlResponse.foldMap(interpretSync) 
// List(a, b, c, d, b, c) 
+0

아, 알겠습니다. 나는 피터 (Peter)에게 통역사의 실행과 개념적으로 리프팅 정의를 혼동하는 것처럼 보입니다. – kostja

+0

@ peter-neyens 우리가 대수를 결합 할 때, 우리는 두 명의 해석기를 결합 할 수 있습니까? 하나는 ID와 다른 미래를 반환합니까? – arjunswaj

+0

두 통역사를 결합하려는 방법이 확실하지 않습니다. 당신은 언제나 프로그램을 두 번, 'Id'로 한 번, 'Future'로 한번 해석 할 수 있습니다. –