Является ли Scala Option
и монадой, и функтором?
Насколько я понимаю, функтор — это просто тип данных, который предоставляет следующий API:
Функтор:
wrap (или apply
), который берет примитив и оборачивает его внутри функтора
map
который берет функтор, разворачивает его, применяет какую-то функцию и перепаковывает его
Итак, Option
— функтор. Потому что я могу применить Option
к примитиву, дающему мне Option[T]
. Я также могу map
на Option
получить то, что внутри функтора и переупаковать внутри Option
.
Чем отличается монада? Я думал, что у монады также есть функция apply
и функция map
. Из эта статья я понял, что монада также имеет flatMap
? Что определяется как просто map
, но без переупаковки результата внутри монады? (Или это map
без переупаковки результата внутри функтора?!)
Поскольку Option
поставляет как map
, так и flatMap
, значит ли это, что Option
является и функтором, и монадой?
Вы можете найти эти инфографика(по хорек) полезными, чтобы понять отношения между классами категориальных типов.
Option
сам по себе не является функтором или монадой. Функтор - это тройка (которая удовлетворяет законам функтора); некоторый тип (F
), единичная функция ((A) => F[A]
) и функция отображения ((F[A], A => B) => F[B]
). Монада — это три кортежа (которые удовлетворяют законам монад); некоторый тип (M
), единичная функция ((A) => M[A]
) и функция flatMap (или привязка) ((M[A], A => M[B]) => M[B]
).
@marstran: Scala — это объектно-ориентированный язык, поэтому операции являются частью типа. В этом смысле можно сказать, что Option
— это монада. unit
— это конструктор, map
— это, ну, Option.map
, а flatMap
— это Option.flatMap
.
@ JörgWMittag Я бы не согласился. Одно из основных различий между Классы типов и Подтип заключается в том, что ваш тип больше не это Х, а скорее имеет (уникальный) X, связанный с ним. Кроме того, независимо от того, как вы решите реализовать их в Скала(или на любом языке), это не меняет математического определения концепции. Теперь я намеренно решил опустить это в своем первом комментарии, потому что, даже если я сам считаю эти различия важными, мне было трудно их понять, когда я впервые изучал эти концепции (как мое личное мнение, так и коллег).
@LuisMiguelMejíaSuárez Под «осведомленностью о внутренних контекстах» я думаю, что вы говорите, что flatMap
использует функцию, которая отображает «неподнятый» или примитивный объект в поднятую монаду, тогда как в функторной функции map
функтор сам отвечает за поднятие результат?
@franklin не совсем, позвольте мне расширить то, что я имею в виду, в контексте. Большинство "монады"(кавычки предназначены для обсуждения того, что они на самом деле не монады), таких как Option
, Either
, List
, IO
, обозначаются как Контексты или Эффекты, потому что они инкапсулируют значения чистый, с которыми связан дополнительный побочный эффект. (этот процесс называется овеществление), например, Option
инкапсулирует эффект неполного вычисления или в Другими словами, значение, которое может существовать, а может и не существовать. Надеясь, что это может прояснить, что такое контекст, давайте рассмотрим, почему я имел в виду "осведомленный".
Возьмем следующий пример safeDiv(a: Double, b: Double): Option[Double] = if (b == 0) None else Some(a / b)
. Учитывая это, вы можете создать это val r = safeDiv(10, 5)
. Теперь у вас есть не Двойник, а возможный Двойник. Что само по себе не очень полезно, поскольку теперь вы не можете суммировать его или выполнять какие-либо другие вычисления. Сначала вы можете сказать, о, мне нужно только получить значение, используя getOrElse
с безопасным значением по умолчанию (как 0). Но что, если вы прямо сейчас не знаете, какое значение по умолчанию вы хотите, но все же вам нужно оперировать значением, поэтому вам нужен способ преобразования, не выходя из ctx
Для этой проблемы (чрезвычайно часто) есть простое решение: просто вызовите map[A, B](fa: F[A])(f: A => B): F[B]
, потому что map
позаботится о контексте за вас, и просто примените функцию к внутреннему значению (в данном конкретном случае, если он существует). Таким образом, вы можете сделать val r2 = r.map(_ + 1)
. Отлично, но что, если вы хотите разделить результат на другое число, вы можете использовать map
... но это оставит вас с Опция[Опция[Двойной]], что означает двойную вероятность пропуска, что я сомневаюсь, что вы хотите. Вам действительно нужно просто значение, если оба подразделения преуспели, или просто None.
Введите flatMap[A, B](fa: F[A])(f: A => F[B]): F[B]
, в основном для этой проблемы (тоже очень часто) тоже есть простое решение. flatMap
не только знает контекст начального значения, но также знает, что результат функции сопоставления вернет другой контекст, который необходим "развернуть" для предоставления окончательного результата. Таким образом, теперь вы можете сделать val r3 = r2.flatMap(safeDiv(_, 0))
. Я надеюсь, что это ясно. - Кстати, ради любопытства мы говорим, что Монада — это Функтор, потому что map = flatMap(fa)(a => unit(f(a)))
также flatMap = flatten(map(fa)(f))
(сгладить карту ;))
Итак, flatMap
говорит: «Я знаю, что мой окончательный результат будет заключен в контекст, но я могу получить ввод, который также заключен в контекст, поэтому я разверну ввод, выполню некоторую функцию, а вывод заключу в контекст». Другими словами, он распространяет контекст. Это правильно?
@franklin Да, я бы просто изменил «Я могу получить ввод, который также заключен в контекст» на Я получу (это необязательно) ввод, который также заключен в контекст - «поэтому я разверну ввод, выполню некоторую функцию и оберну вывод в контексте» в конце, да, вы завернете результат в контекст, но результат функции также заключен в контекст, поэтому вам нужно его тоже развернуть. Но да, и map
, и flatMap
распространяют контекст. ИМХО, хорошим упражнением для их понимания является реализация их с нуля для различных типов, таких как Option
, List
и IO
.
Я нашел эти видео очень хорошим написано и очень четким.
Краткий ответ: Да.
Более длинный ответ: каждая монада является аппликативным функтором, и каждый аппликативный функтор является функтором. В объектно-ориентированных терминах: Monad <: Applicative <: Functor.
Каждый Монада также является Функтор. Не только
Option
, а любой Монада. - Во-вторых,apply
не из Функтор, а из аппликативный(что также означает, что каждый аппликативный также является Функтор, а каждый Монада также является апликативный). - В-третьих, да, монада определяется наличием такжеflatMap
, которую лучше рассматривать какmap
, которая осознает внутренний контекст, или какmap
, за которой следуетflatten
. Но, ИМХО, лучший способ различать их — это типы,map[A, B](fa: F[A])(f: A => B): F[B]
VSflatMap[A, B](fa: F[A])(f: A => F[B]): F[B]
.