Почему монады не складываются в scala

Почему монады не составляют, если монада является аппликативом, а аппликатив - функтором. Вы видите эту цепочку наследования во многих статьях в сети (через которые я прошел). Но когда функторы и аппликативы составляют, почему монады нарушают это?

Может ли кто-нибудь предоставить простой пример на scala, демонстрирующий эту проблему? Я знаю, что об этом много спрашивают, но трудно понять без простого примера.


person Jay    schedule 15.10.2015    source источник
comment
Возможный дубликат Аппликативы составляют, а монады - нет   -  person ziggystar    schedule 15.10.2015
comment
Это ответ haskell не для scala   -  person Jay    schedule 15.10.2015
comment
Ознакомьтесь с ответом Конала. Это языковой агностик. Монады - это математическое понятие, и они не различаются между языками.   -  person ziggystar    schedule 15.10.2015
comment
Я знаю, но думаю, что для получения ответа на этот вопрос не очень полезно направлять людей изучать другой язык с другим устаревшим синтаксисом. Большинство учебных материалов по Monad написано на языке haskell (который трудно читать, если вы с ним не знакомы), а также здесь много вопросов по SO. Но я прямо попросил дать в scala хорошее объяснение этой конкретной проблемы. Я уверен, что многие это оценят. Таким образом, этот вопрос становится еще одним перенаправлением на что-то еще ...   -  person Jay    schedule 16.10.2015
comment
Вы не читали связанный ответ, но написали 5 строк жалоб. Ответ короче, чем ваш комментарий, и не содержит ни одной строки Haskell.   -  person ziggystar    schedule 16.10.2015
comment
Затем он снова попросил код, демонстрирующий проблему, а не абстрактный ответ. И он попросил код scala. Это разумный вопрос. Часто легче понять что-то на конкретном примере, и только тогда вы действительно можете обобщить эти знания.   -  person Régis Jean-Gilles    schedule 19.10.2015
comment
Я действительно провел небольшое исследование, прежде чем задать этот вопрос. И связанный ответ Конала говорит, что монады составляют, но результатом может быть не монада. Это сложно понять без какого-либо примера.   -  person Jay    schedule 19.10.2015
comment
@ziggystar «не зависящие от языка математические концепции» только теоретически, хе-хе. Как насчет того, чтобы все эти функторы на самом деле были эндофункторами, а монады по умолчанию были сильными монадами в Haskell / Scala, в то время как я предполагаю, что некоторые эзотерические «языки пространства» (с топоями и дерьмом) реализуют их такими, какие они есть на самом деле.   -  person dk14    schedule 02.08.2018


Ответы (2)


Во-первых, начнем с простой задачи. Скажем, нам нужно получить сумму двух целых чисел, каждое из которых заключено в 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

Applicatives, однако, не имеют таких проблем - вы можете легко их составить, но имейте в виду, что результат композиции двух 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