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

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

Может кто-нибудь привести простой пример в 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,

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

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

Другие вопросы по тегам