Как совместить последовательность Клейсли

Учитывая метод, который возвращает 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)

Должны ли вы на самом деле это сделать? Опять же, вероятно, нет, но это работает, и это круто иметь возможность отображать сложные вычисления, подобные этому.

Другие вопросы по тегам