问题描述
给定一个返回 Kleisli
给定参数的方法
Given a method that return a Kleisli
given a parameter
def k[A, B, C](a: A) : Kleisli[Future, B, Option[C]] = ???
我想写一个组合子来处理这个参数的序列
I want to write a combinator that deals with a sequence of this parameter
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
实例代码>(请参阅我的回答此处,以了解为什么不这样做的一些讨论).
Note that I'm using List
instead of Seq
since Scalaz doesn't provide a Traverse
instance for Seq
(see my answer here for some discussion of why it doesn't).
这是首先使用 Kleisli
的一大优势——如果 F
有一个 Applicative
实例,那么Kleisli[F, In, ?]
用于任何 In
.在这种情况下,这意味着您可以使用 traverse
而不是使用 map
和 Future.sequence
手动排序.
This is one of the big advantages of using Kleisli
in the first place—if F
has an Applicative
instance, then so does Kleisli[F, In, ?]
for any In
. In this case that means that you can use traverse
instead of manually sequencing with map
and Future.sequence
.
更新:想在这里变得超级花哨吗?可能不是,但为了以防万一,您实际上可以在最后一步进行抽象并将返回类型的上下文移动到 Kleisli
的上下文中:
Update: want to get super-fancy here? Probably not, but just in case, you could actually take the abstraction one last step and move the context of the return type into the context of the 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
应用到输入值后,我们将在未来在列表中获得该结果的事实.
This allows us to map, etc. directly over the result type while ignoring the fact that we're getting that result in a list in a future after applying the Kleisli
to an input value.
具体:
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)
你真的应该这样做吗?同样,可能不会,但它有效,并且能够将方式映射到这样的复杂计算中是很酷的.
Should you actually do this? Again, probably not, but it works, and it is kind of cool to be able to map way into a complex computation like this.
这篇关于如何组合 Kleisli 的序列的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!