如何组合 Kleisli 序列

How to combine sequence of Kleisli

给定一个方法return一个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)
}

有没有更好的方法?

有更好的方法:

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 没有为 Seq 提供 Traverse 实例(参见我的回答 here 讨论为什么不这样做)。

这是首先使用 Kleisli 的一大优势——如果 F 有一个 Applicative 实例,那么 Kleisli[F, In, ?] 对任何 In。在这种情况下,这意味着您可以使用 traverse 而不是使用 mapFuture.sequence.

手动排序

更新:想在这里得到超级喜欢?可能不是,但为了以防万一,您实际上可以进行最后一步抽象并将 return 类型的上下文移动到 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)

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