How to combine sequence of Kleisli

拜拜、爱过 提交于 2020-01-02 10:33:38

问题


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)
}

Is there a better way ?


回答1:


There is a better way:

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)

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).

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.


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))

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.

Concretely:

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))

And then:

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)

And the punchline:

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.



来源:https://stackoverflow.com/questions/36487123/how-to-combine-sequence-of-kleisli

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!