2016-06-13 6 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

すぐにtypelevelインキュベーターになるhttp://47deg.github.io/fetch/という47度の図書館に向けることができますか?心配してください、私は47度で働いていませんが、これはすでにあなたがやりたいことの多くのための解決策を持っているようです。 – wheaties

答えて

7

抽象データ型Opsはをフェッチしストア複数Record Sへに代数を定義します。それは2つの操作について説明していますが、それは代数が行うべき唯一のことです。操作が実際にどのように実行されるかは、FetchStoreには関係ありません。唯一有益なのはそれぞれList[Record]List[Response]です。

期待される結果の型をFetchStoreとし、Future[List[Record]]]とすることで、この代数の解釈方法を制限することになります。あなたのテストでは、Webサービスやデータベースに非同期で接続し、単に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

ああ、私は、完璧な意味を理解します。私は概念的には、読者の定義を混乱させているように見える。 – kostja

+0

@ peter-neyens私たちは代数を組み合わせると、2人のインタープリタを結合できますか? – arjunswaj

+0

2つのインタプリタをどのように組み合わせるかはわかりません。あなたはいつもプログラムを2回、「Id」に1回、「Future」に1回解釈することができます。 –

関連する問題