2016-06-13 24 views
6

我已经为使用免费monad的ETL过程实现了一种简单的语言。当使用List作为数据读取和存储的输入和输出时,一切工作正常。不过我使用异步图书馆和Future[List]如何在将来使用免费monad [M [_]]

常见的进口工作和定义

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] 

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]

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 
} 

工作如预期从...返回在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] => 

我目前的解决方法是让store接受Future[List[Record]],并让翻译地图上的Future,但感觉笨拙。

该问题不是特定于List - Option也会有用。

我做错了吗?这是否有某种单体变压器?

+0

这似乎是一个monad变压器的典型模式,第一次看起来像是Haskell有一个'FreeT',但是无法在Scalaz或猫中找到它。 –

+3

从[7.2.0]开始,scalaz拥有'FreeT'(https://oss.sonatype.org/service/local/repositories/archive/org/scalaz/scalaz_2.11/7.2.0/scalaz_2.11-7.2的3.0 javadoc.jar /!/ index.html的#scalaz.FreeT)。 –

+1

我可以指向47度的图书馆吗?它恰当地命名为http://47deg.github.io/fetch/,它很快将成为一个类型级的孵化器?请注意,我没有为47度工作,但它似乎已经为您想要做的大部分工作提供了解决方案。 – wheaties

回答

7

的抽象数据类型Ops限定代数商店多个Record秒。它描述了两种操作,但这也是代数应该做的唯一的事情。如何操作实际执行,不应该在所有FetchStore没关系,你想到的唯一有用的东西是分别为List[Record]List[Response]

通过使期望的结果类型为FetchStore a 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 S'这是我们的口译员的工作:

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当我们结合代数时,我们是否可以合并两个解释器 - 一个返回的Id和其他未来的未来? – arjunswaj

+0

我不确定你想以何种方式组合两名口译员。你总是可以解释一个节目两次,一次是“Id”,一次是“Future”。 –