Как совместить последовательность Клейсли
Учитывая метод, который возвращает 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 ответ
Существует лучший способ:
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
так как Скалаз не предоставляет Traverse
экземпляр для Seq
(см. мой ответ здесь для обсуждения того, почему это не так).
Это одно из больших преимуществ использования Kleisli
во-первых, если F
имеет Applicative
Например, то же самое Kleisli[F, In, ?]
для любого In
, В этом случае это означает, что вы можете использовать traverse
вместо ручной последовательности с map
а также Future.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)
Должны ли вы на самом деле это сделать? Опять же, вероятно, нет, но это работает, и это круто иметь возможность отображать сложные вычисления, подобные этому.