Является ли коллекция с flatMap монадой?

у Scala есть черта Iterable[A] что определяет

def flatMap[B](f: (A) ⇒ GenTraversableOnce[B]): Iterable[B] 

Это точно выглядит как функция bind на монаде, и документация намекает, что это монада, но есть два возражения, одно незначительное и одно большое:

  • второстепенный: возвращаемый тип переданной функции-это GenTraversableOnce. Я думаю, что это просто удобство, которое можно упустить из виду, оценивая монадность.
  • главное одно: "значение" монада-это список всех значений, которые он содержит, но функция получает значения по одному.

нарушают ли эти проблемы монадность коллекции?

5 ответов


на "главный" вопрос легче ответить: Нет, это не так, потому что это не то, что он означает. Монада не обязана иметь какое-либо конкретное "значение" или нет, только составлять с функциями определенным образом.

для "несовершеннолетнего" вы правы, что беспокоитесь о типах. Правильно, монада-это моноид (с некоторыми дополнительными ограничениями), то есть это набор с определенными операциями. Элементы этого набора, насколько я могу судить, имеют тип A => M[B] (in scalaz этот тип называется Kleisli); flatMap - это |+| работа моноидом.

делает набор все возможно A => Iterable[B] в Scala образуют моноид по отношению к этой операции (и подходящий выбор идентичности)? Нет, Очень нет, потому что есть много возможных A => Iterable[B], которые нарушают законы монады. Для тривиального примера, {a: A => throw new RuntimeException()}. Более серьезным примером является то, что, например, если Set присутствует flatMap цепь, это может сломать ассоциативность: предположим, мы имеем:

f: String => Iterable[String] = {s => List(s)}
g: String => Iterable[String] = {s => Set(s)}
h: String => Iterable[String] = {s => List("hi", "hi")}

затем

((f |+| g) |+| h).apply("hi") = List("hi") flatMap h = List("hi", "hi")

но

(f |+| (g |+| h)).apply("hi") = List("hi") flatMap {s => Set("hi")} = List("hi")

что расстраивает, потому что весь смысл моноида в том, что мы можем писать f |+| g |+| h и не беспокойтесь о том, как мы его оцениваем. Возвращаясь к монадам, дело в том, что мы должны уметь писать

for {
  a <- f("hi")
  b <- g(a)
  c <- h(b)
} yield c

и не беспокойтесь о том, какой заказ flatMaps составлены внутри. Но для f, g и h сверху, какой ответ вы ожидаете выше кода давать? (Я знаю ответ, но это довольно удивительно). С истинной монадой вопрос не возникнет, кроме как детали реализации компилятора scala, потому что ответ будет одинаковым в любом случае.

С другой стороны, делает определенное подмножество возможно A => M[B], например, "множество всех A => List[B] реализовано под скалацци безопасное подмножество scala", образуют монаду по отношению к этому определению flatMap? Да (по крайней мере для общепринятое определение того, когда две функции scala равны). И есть несколько подмножеств, к которым это относится. Но я думаю, что не совсем верно говорить, что scala Iterables в общем образуют монаду под flatMap.


ответ на ваш вопрос заголовка нет. Коллекция с flatMap недостаточно быть монадой. Это может быть монадой, если она удовлетворяет некоторые дополнительные условия.

"мелкие" проблемы, конечно, ломает monadicity (правильное слово "монада-Несс") из Iterable. Это потому, что многие подтипы Iterable и GenTraversableOnce are не монады. Следовательно,Iterable - это не монада.

ваш "главный" вопрос вообще не проблема. Например, аргумент функции List монады-это flatMap получает элементами List одному за раз. Каждый элемент списка генерирует целый список результатов, и все эти списки объединяются вместе в конце.

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

требования для того чтобы быть монада!--60-->
  1. монада должна быть конструктором типа F[_] для этого требуется один аргумент типа. Например, F может быть List, Function0, Option, etc.
  2. в монадическом блок. Это функция, которая принимает значение любого типа A и возвращает значение типа F[A].
  3. в монадическом состав операции. Это операция, которая принимает функцию типа A => F[B], и функции типа B => F[C] и производит составную функцию типа A => F[C].

(есть и другие способы заявить об этом, но я нахожу эту формулировку простой для объяснения)

рассмотрим эти Iterable. Это определенно требует одного аргумента типа. Он имеет своего рода единицу в функции Iterable(_). А пока его flatMap деятельность строго не соответствует, мы смогли определенно написать:

def unit[A](a: A): Iterable[A] = Iterable(a)

def compose[A,B,C](f: A => Iterable[B],
                   g: B => Iterable[C]): A => Iterable[C] =
  a => f(a).flatMap(g)

но это не делает его монада, так как монада дополнительно удовлетворить определенные законы:

  1. ассоциативность: compose(compose(f, g), h) = compose(f, compose(g, h))
  2. идентичность: compose(unit, f) = f = compose(f, unit)

легкий способ нарушить эти законы,как уже указывал lmm - это смешать Set и List как Iterable в этих выражениях.

"Semimonads"

в то время как тип конструкции с просто flatMap (а не unit), не является монадой, она может образовывать то, что называется Kleisli semigroupoid. Требования такие же, как для монады, за исключением unit операции и без закона тождества.

(замечание по терминологии: монада образует категория Kleisli и semigroupoid это категория без идентификаторов.)

для выделения

Scala для-понимания технически имеют даже меньше требования чем полугруппоиды (просто map и flatMap операции подчиняясь нет законы). Но, используя их с вещами, которые не менее semigroupoids очень странные и удивительные эффекты. Например, это означает, что вы не можете встроить определения в for-comprehension. Если бы ты ... --48-->

val p = for {
  x <- foo
  y <- bar
} yield x + y

и определение foo были

val foo = for {
  a <- baz
  b <- qux
} yield a * b

если закон ассоциативности не действует, мы не может полагайтесь на возможность переписать это as:

val p = for {
  a <- baz
  b <- qux
  y <- bar
} yield a * b + y

невозможность сделать такого рода подстановку крайне противоречит интуиции. Поэтому большую часть времени, когда мы работаем с for-comprehensions, мы предполагаем, что мы работаем в монаде (вероятно, даже если мы этого не знаем) или, по крайней мере, полугруппоиде Клейсли.

но обратите внимание, что этот вид замены не работает вообще для Iterable:

scala> val bar: Iterable[Int] = List(1,2,3)
bar: Iterable[Int] = List(1, 2, 3)

scala> val baz: Iterable[Int] = Set(1,2,3)
baz: Iterable[Int] = Set(1, 2, 3)

scala> val qux: Iterable[Int] = List(1,1)
qux: Iterable[Int] = List(1, 1)

scala> val foo = for {
     |   x <- bar
     |   y <- baz
     | } yield x * y
foo: Iterable[Int] = List(1, 2, 3, 2, 4, 6, 3, 6, 9)

scala> for {
     |   x <- foo
     |   y <- qux
     | } yield x + y
res0: Iterable[Int] = List(2, 2, 3, 3, 4, 4, 3, 3, 5, 5, 7, 7, 4, 4, 7, 7, 10, 10)

scala> for {
     |   x <- bar
     |   y <- baz
     |   z <- qux
     | } yield x * y + z
res1: Iterable[Int] = List(2, 3, 4, 3, 5, 7, 4, 7, 10)

для получения дополнительной информации о монадах

подробнее о монады в скале, в том числе, что это все означает и почему мы должны заботиться, я призываю вас взглянуть на Глава 11 моей книги.


Я думаю, что коллекция с flatMap не обязательно является монадой. Это не обязательно соответствует законы монады. Эти законы, вероятно, лучше объяснить в функциональное программирование в Scala чем я мог сделать.

недавно я услышал от коллеги упрощенное и прагматичное объяснение (с самосознанием) того, что такое монада в Scala: something you can put in a for comprehension.

Я не эксперт монады, но мне кажется, что это не так, и поэтому это для коллекции с flatMap. Самый очевидный пример этого - в Scala lib Either поскольку он не является правильным biaised и не имеет никакого метода flatMap, пока вы не проецируете его в сторону (и эта проекция не является монадической, поскольку она возвращается). Насколько я понимаю, тип не является монадой (или моноидом или чем-то еще), но тип может иметь монаду (или даже много? не уверен, но был бы заинтересован любым исключением (но, может быть, либо хороший?)).

Я думаю, что Scala является прагматичным язык, в котором иногда может быть полезно забыть о строгих правилах и помочь программистам легче выполнять свою работу. Не все программисты заботятся о том, что такое монада, но многие, вероятно, хотят сгладить List[Set[Int]] в какой-то момент и flatMap может помочь им.

это напоминает мне об этом сообщении в блоге, в котором будущий тип рассматривается как copointed для тестов.


чтобы ответить на ваш вопрос в контексте основных Скала кроме Scalaz и категории теории, пока Скала не имеет признака, класс или объект с именем "Монада", его реализует объектно-ориентированную концепцию монады, что я буду ссылаться как Orderskian монады, поскольку он был изобретен и внедрен в первую очередь Мартин Ordersky (и Эдриан мавров по данным http://igstan.ro/posts/2012-08-23-scala-s-flatmap-is-not-haskell-s.html).

в Orderskian монады требуется, по крайней мере, map, flatmap и withfilter функции, как описано в "программирование в Scala" (2Ed:PDF edition:Глава 23:страница 531) Мартина Одерского, где он заявляет: "поэтому map, flatMap и withFilter можно рассматривать как объектно-ориентированную версию функциональной концепции монады."Исходя из этого, Scala коллекций Orderskian монады.

чтобы ответить на ваш вопрос, включая Scalaz, требуется scalaz.Implementatation монады продлить черта монад и реализовать два абстрактных функции, чистые и связывающие, чтобы удовлетворить трем законам, требующим их (http://scalaz.github.io/scalaz/scalaz-2.9.1-6.0.2/doc/index.html#scalaz.Monad). Основные коллекции Scala не отвечают этим требованиям, поэтому ничто не может сломать их scalaz.Монадность, потому что ее никогда не существовало. До такой степени, что scalaz.Монада моделирует теорию категорий монада, этот аргумент применим к последней.


методы коллекции Scala flatMap и flatten более мощные, чем монадические flatMap/flatten. Смотрите здесь: https://www.slideshare.net/pjschwarz/scala-collection-methods-flatmap-and-flatten-are-more-powerful-than-monadic-flatmap-and-flatten

enter image description here