Почему монады не сочиняются в скалах
Почему монады не сочиняются, когда монада является аппликативным, а аппликативный - функтором. Вы видите эту цепочку наследования во многих статьях в Интернете (которые я прошел). Но когда функторы и аппликативы сочиняют, почему монады ломают это?
Может кто-нибудь привести простой пример в scala, который демонстрирует эту проблему? Я знаю, что об этом часто спрашивают, но трудно понять без простого примера.
2 ответа
Во-первых, давайте начнем с простой проблемы. Допустим, нам нужно получить сумму из двух целых чисел, каждое из которых заключено в оба Future
а также Option
, Давайте принимать cats
библиотека для того, чтобы напоминать стандартные определения библиотеки Haskell с использованием Scala-синтаксиса.
Если мы используем монадный подход (иначе flatMap
), нам нужно:
- и то и другое
Future
а такжеOption
должен иметьMonad
экземпляры, определенные над ними - нам также нужен монадический трансформатор
OptionT
который будет работать только дляOption
(точноF[Option[T]]
)
Итак, вот код (давайте забудем о простом понимании и упрощении):
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
и т.п.
Здесь важно то, что нет FutureT
определяется в кошках, так что вы можете составить Future[Option[T]]
, но не могу сделать это с Option[Future[T]]
(позже я покажу, что эта проблема еще более общая).
С другой стороны, если вы выбираете композицию, используя Applicative
Вам нужно будет выполнить только одно требование:
- и то и другое
Future
а такжеOption
должен иметьApplicative
экземпляры, определенные над ними
Вам не нужны специальные трансформаторы для Option
в основном кошка библиотека предоставляет 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
определяется в кошках / скалазах, но не FutureT
,
В общем ( отсюда):
К сожалению, наша настоящая цель, составление монад, гораздо сложнее. Фактически, мы можем фактически доказать, что в определенном смысле нет способа построить функцию соединения с вышеприведенным типом, используя только операции двух монад (см. Приложение для краткого изложения доказательства). Отсюда следует, что единственный способ, которым мы могли бы надеяться сформировать композицию, - это наличие некоторых дополнительных конструкций, связывающих два компонента.
И эта композиция даже не является необходимой коммутативной (заменяемой), как я продемонстрировал для Option
а также Future
,
В качестве упражнения вы можете попытаться определить 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
(на нем не определена comonad), по крайней мере, в "неблокирующем" контексте (например, ScalaJs). Это в основном означает, что вы не можете "поменяться" Future
а также F
, лайк Future[F[Future[B]] => F[Future[Future[B]
, Последнее является естественной трансформацией (морфизм между функторами), поэтому это объясняет первый комментарий к этому общему ответу:
Вы можете составить монады, если можете обеспечить естественный обмен преобразованиями: N M a -> M N a
Applicative
Однако таких проблем нет - их легко составить, но имейте в виду, что результат сложения двух Applicatives
не может быть монадой (но всегда будет аппликативной). Nested[Future, Option, T]
это не монада на T
независимо от того, что оба Option
а также Future
монады на T
, Проще говоря, вложенный в класс не имеет flatMap
,
Было бы также полезно прочитать:
- http://typelevel.org/cats/tut/applicative.html
- http://typelevel.org/cats/tut/apply.html
- http://typelevel.org/cats/tut/monad.html
- http://typelevel.org/cats/tut/optiont.html
Собираем все вместе (F
а также G
монады)
F[G[T]]
это монадаG[T]
, но не наT
G_TRANSFORMER[F, T]
требуется для того, чтобы получить монаду наT
отF[G[T]]
,- здесь нет
MEGA_TRANSFORMER[G, F, T]
поскольку такой трансформатор не может быть построен поверх монады - он требует дополнительных операций, определенных наG
(похоже на комонаду наG
должно быть достаточно) - каждая монада (в том числе
G
а такжеF
) аппликативен, но не каждый аппликатив является монадой - теоретически
F[G[T]]
является аппликативным по обоимG[T]
а такжеT
, Однако для создания Scala требуетсяNESTED[F, G, T]
для того, чтобы составить аппликативную наT
(который реализован в библиотеке кошек). NESTED[F, G, T]
аппликативен, но не монада
Это означает, что вы можете сочинять Future x Option
(ака Option[Future[T]]
) к одной монаде (потому что OptionT
существует), но вы не можете сочинять Option x Future
(ака Future[Option[T]]
) не зная, что будущее - это нечто иное, кроме того, чтобы быть монадой (даже если они по своей природе являются аппликативными функторами - аппликативного недостаточно для того, чтобы ни построить монаду, ни монадный преобразователь на нем). В принципе:
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]
,но вы можете объединить любые две монады (по своей сути функторы) только в одну аппликативную (но не в монаду).
Тони Моррис выступил с докладом о монадных трансформаторах, который очень хорошо объясняет эту проблему.
http://tonymorris.github.io/blog/posts/monad-transformers/
Он использует haskell, но примеры легко переводятся в scala.