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


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

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

2 10

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.

Было бы также полезно прочитать:

Собирая все это вместе (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.