Во-первых, начнем с простой задачи. Скажем, нам нужно получить сумму двух целых чисел, каждое из которых заключено в Future
и Option
. Возьмем cats
библиотеку, чтобы напоминать определения стандартной библиотеки Haskell с синтаксисом Scala.
Если мы используем монадный подход (он же flatMap
), нам понадобятся:
- и
Future
, и Option
должны иметь Monad
экземпляры, определенные над ними
- нам также нужен монадический преобразователь
OptionT
, который будет работать только для Option
(а именно F[Option[T]]
)
Итак, вот код (давайте забудем о for-computing и подъеме, чтобы упростить его):
val fa = OptionT[Future, Int](Future(Some(1)))
val fb = OptionT[Future, Int](Future(Some(2)))
fa.flatMap(a => fb.map(b => a + b)) //note that a and b are already Int's not Future's
если вы посмотрите OptionT.flatMap
источники:
def flatMap[B](f: A => OptionT[F, B])(implicit F: Monad[F]): OptionT[F, B] =
flatMapF(a => f(a).value)
def flatMapF[B](f: A => F[Option[B]])(implicit F: Monad[F]): OptionT[F, B] =
OptionT(F.flatMap(value)(_.fold(F.pure[Option[B]](None))(f)))
Вы заметите, что код довольно специфичен для внутренней логики и структуры Option
(fold
, None
). Та же проблема для EitherT
, StateT
и т. Д.
Здесь важно то, что для cats не определено FutureT
, поэтому вы можете составить Future[Option[T]]
, но не можете этого сделать с Option[Future[T]]
(позже я покажу, что эта проблема еще более общая).
С другой стороны, если вы выберете композицию с использованием Applicative
, вам нужно будет выполнить только одно требование:
- и
Future
, и Option
должны иметь Applicative
экземпляров, определенных над ними
Вам не нужны какие-либо специальные преобразователи для Option
, в основном библиотека cats предоставляет Nested
класс, который работает для любого Applicative
(для упрощения понимания давайте забудем о сахаре прикладного конструктора):
val fa = Nested[Future, Option, Int](Future(Some(1)))
val fb = Nested[Future, Option, Int](Future(Some(1)))
fa.map(x => (y: Int) => y + x).ap(fb)
Поменяем местами Вариант и Будущее:
val fa = Nested[Option, Future, Int](Some(Future(1)))
val fb = Nested[Option, Future, Int](Some(Future(1)))
fa.map(x => (y: Int) => y + x).ap(fb)
Работает!
Итак, да, монада является аппликативной, Option[Future[T]]
по-прежнему является монаадой (на Future[T]
, но не на самом T
), но она позволяет вам работать только с Future[T]
, а не с T
. Чтобы «объединить» Option
с Future
слоями - вы должны определить монадический преобразователь FutureT
, чтобы объединить Future
с Option
- вы должны определить OptionT
. И OptionT
определен в cats / scalaz, но не FutureT
.
В общем (из здесь):
К сожалению, наша настоящая цель - составление монад - намного сложнее. .. Фактически, мы действительно можем доказать, что в определенном смысле нет способа построить функцию соединения с указанным выше типом, используя только операции двух монад (схему доказательства см. В приложении). Отсюда следует, что единственный способ, которым мы могли бы надеяться сформировать композицию, - это наличие дополнительных конструкций, связывающих два компонента.
И этот состав даже не обязательно коммутативный (заменяемый), как я продемонстрировал для Option
и Future
.
В качестве упражнения вы можете попробовать определить flatMap FutureT
:
def flatMapF[B](f: A => F[Future[B]])(implicit F: Monad[F]): FutureT[F, B] =
FutureT(F.flatMap(value){ x: Future[A] =>
val r: Future[F[Future[B]] = x.map(f)
//you have to return F[Future[B]] here using only f and F.pure,
//where F can be List, Option whatever
})
в основном проблема с такой реализацией заключается в том, что вам нужно «извлечь» значение из r, что здесь невозможно, предполагая, что вы не можете извлечь значение из Future
(на нем не определена комонада), по крайней мере, в «неблокирующем» контексте (например, ScalaJs). Это в основном означает, что вы не можете «поменять местами» Future
и F
, как Future[F[Future[B]] => F[Future[Future[B]
. Последнее является естественным преобразованием (морфизм между функторами), поэтому он объясняет первый комментарий к этому общему ответу:
вы можете составлять монады, если можете обеспечить естественный обмен преобразованием: N M a -> M N a
Applicative
s, однако, не имеют таких проблем - вы можете легко их составить, но имейте в виду, что результат композиции двух Applicatives
может не быть монадой (но всегда будет аппликативным). Nested[Future, Option, T]
не является монадой на T
, несмотря на то, что и Option
, и Future
являются монадами на T
. Простыми словами Вложен как class не имеет flatMap
.
Также было бы полезно прочитать:
Собираем все вместе (F
и G
- монады)
F[G[T]]
- это монада на G[T]
, но не на T
G_TRANSFORMER[F, T]
требуется для получения монады на T
от F[G[T]]
.
- нет
MEGA_TRANSFORMER[G, F, T]
, так как такой преобразователь не может быть построен поверх монады - он требует дополнительных операций, определенных на G
(похоже, что comonad на G
должно быть достаточно)
- каждая монада (включая
G
и F
) является аппликативной, но не каждая аппликативная монада
- теоретически
F[G[T]]
является аппликативом над G[T]
и T
. Однако scala требует создания NESTED[F, G, T]
, чтобы составить аппликатив на T
(который реализован в библиотеке cats).
NESTED[F, G, T]
аппликативный, но не монада
Это означает, что вы можете составить Future x Option
(он же Option[Future[T]]
) в одну монаду (coz OptionT
существует), но вы не можете составить Option x Future
(он же Future[Option[T]]
), не зная, что Future - это что-то еще, кроме монады (даже если они по своей сути аппликативны). функторы - аппликативности недостаточно, чтобы построить на ней монаду или монадный преобразователь). В основном:
OptionT
можно рассматривать как некоммутативный бинарный оператор, определенный как OptionT: Monad[Option] x Monad[F] -> OptionT[F, T]; for all Monad[F], T; for some F[T]
. Или вообще: Merge: Monad[G] x Monad[F] -> Monad[Merge]; for all T, Monad[F]; but only for **some of Monad[G]**, some F[T], G[T]
;
вы можете объединить любые два аппликатива в один аппликативный Nested: Applicative[F] x Applicative[G] -> Nested[F, G]; for all Applicative[F], Applicative[G], T; for some F[T], G[T]
,
но вы можете составить любые две монады (по сути, функторы) только в одну аппликативную (но не в монаду).
person
dk14
schedule
17.09.2016