如何结合Kleisli的序列

时间:2016-04-07 21:01:08

标签: scala scalaz

给定一个方法,在给定参数

的情况下返回Kleisli
def k[A, B, C](a: A) : Kleisli[Future, B, Option[C]] = ???

我想编写一个处理此参数序列的组合器

def ks[A, B, C](as: Seq[A]) : Kleisli[Future, B, Seq[C]] = Kleisli[Future, B, Seq[C]] {
  ctx => Future.sequence(as.map(a => k(a).run(ctx))).map(_.flatten)
}

有更好的方法吗?

1 个答案:

答案 0 :(得分:3)

有一种更好的方法:

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
import scalaz._, Scalaz._

def k[A, B, C](a: A): Kleisli[Future, B, Option[C]] = ???

def ks[A, B, C](as: List[A]): Kleisli[Future, B, List[C]] =
  as.traverseU(k[A, B, C]).map(_.flatten)

请注意,我使用的是List而不是Seq,因为Scalaz没有为Traverse提供Seq个实例(请参阅我的回答here讨论为什么不这样做。

这是首先使用Kleisli的一大优势 - 如果FApplicative个实例,那么任何Kleisli[F, In, ?] In也是如此}}。在这种情况下,这意味着您可以使用traverse而不是使用mapFuture.sequence手动排序。

更新:想在这里超级幻想?可能不是,但为了以防万一,您实际上可以采取抽象的最后一步,并将返回类型的上下文移动到Kleisli的上下文中:

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
import scalaz._, Scalaz._

type FutureOption[x] = OptionT[Future, x]
type FutureList[x] = ListT[Future, x]

def k[A, B, C](a: A): Kleisli[FutureOption, B, C] = ???

def ks[A, B, C](as: List[A]): Kleisli[FutureList, B, C] =
  Kleisli.kleisliMonadTrans[B].liftMU(
    ListT.fromList(as.point[Future])
  ).flatMap(k[A, B, C](_).mapK[FutureList, C](_.toListT))

这允许我们直接在结果类型上进行映射等,同时忽略在将Kleisli应用于输入值后我们将来在列表中获得该结果的事实。

具体地:

import scala.util.Try

def k(s: String): Kleisli[FutureOption, Int, Int] = Kleisli[FutureOption, Int, Int] { in =>
  OptionT(Future(Try(s.toInt + in).toOption))
}

def ks(as: List[String]): Kleisli[FutureList, Int, Int] =
  Kleisli.kleisliMonadTrans[Int].liftMU(
    ListT.fromList(as.point[Future])
  ).flatMap(k(_).mapK[FutureList, Int](_.toListT))

然后:

import scala.concurrent.Await
import scala.concurrent.duration._

scala> import scala.concurrent.Await
import scala.concurrent.Await

scala> import scala.concurrent.duration._
import scala.concurrent.duration._

scala> Await.result(ks(List("1", "2", "a", "3")).run(0).run, 1.second)
res0: List[Int] = List(1, 2, 3)

和妙语:

scala> val mapped = ks(List("1", "2", "a", "3")).map(_ + 1)
mapped: scalaz.Kleisli[FutureList,Int,Int] = Kleisli(<function1>)

scala> Await.result(mapped.run(0).run, 1.second)
res1: List[Int] = List(2, 3, 4)

你真的应该这样做吗?同样,可能不是,但是它有效,并且能够将方式映射到这样的复杂计算中是一种很酷的。