Почему монады не сочиняют в скале
Почему монады не составляют, когда Монада является Аппликативом, а Аппликатив-функтором. Вы видите эту цепочку наследования во многих статьях в интернете (которые я просматривал ). Но когда функторы и аппликаторы составляют, почему монады нарушают это ?
Может ли кто-нибудь привести простой пример в scala, который демонстрирует эту проблему ? Я знаю, что об этом много спрашивают, но как-то трудно понять без простого примера.
2 ответа:
Во-первых, давайте начнем с простой задачи. Предположим, нам нужно получить сумму двух целых чисел, каждое из которых обернуто в
Future
иOption
. Возьмем библиотекуcats
, чтобы она напоминала стандартные определения библиотек Хаскелла с помощью Scala-синтаксиса.Если мы используем монадный подход (он же
flatMap
), нам нужно:
- и
Future
, иOption
должны иметьMonad
экземпляры, определенные над ними- нам также нужен монадический трансформатор
OptionT
, который будет работать только дляOption
(точноF[Option[T]]
)Итак, вот код (давайте забудем о for-understanding и подъеме, чтобы сделать его проще):
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
.Вообще (из здесь):
К сожалению, наша реальная цель, состав монад, гораздо больше трудный. .. На самом деле, мы действительно можем доказать это, в определенном смысле, нет никакого способа построить функцию соединения с типом выше, используя только операции двух монад (см. доказательства). Из этого следует, что единственный путь, который мы могли бы надеяться сформировать композиция - это если есть какие-то дополнительные конструкции, связывающие два компонента
И эта композиция даже не нужна коммутативный (swappable), как я продемонстрировал для
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
(похоже, что 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]]
) в одну единственную монаду (потому что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.