Монада на простом английском языке? (Для программиста OOP без фона FP)

В терминах, которые программист ООП понимал бы (без какой-либо подготовки функционального программирования), что такое монада?

Какую проблему он решает и какие наиболее распространенные места он использует?

EDIT:

Чтобы прояснить понимание, которое я искал, скажем, вы конвертировали приложение FP, в котором были монады в приложение ООП. Что бы вы сделали, чтобы передать обязанности монадов в приложение ООП?

Ответ 1

ОБНОВЛЕНИЕ: Этот вопрос был темой очень длинной серии блогов, которую вы можете прочитать на Monads - спасибо за отличный вопрос!

С точки зрения того, что программист ООП понимает (без какой-либо функциональной основы программирования), что такое монада?

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

Во-первых, что такое "усилитель типов"? Под этим я подразумеваю некоторую систему, которая позволяет вам брать тип и превращать его в более специальный тип. Например, в С# рассмотрим Nullable<T>. Это усилитель типов. Он позволяет вам взять тип, скажем, int, и добавить к нему новую возможность, а именно, что теперь он может быть нулевым, если не мог раньше.

В качестве второго примера рассмотрим IEnumerable<T>. Это усилитель типов. Он позволяет вам взять тип, скажем, string, и добавить к этому типу новую возможность, а именно то, что теперь вы можете создавать последовательность строк из любого числа отдельных строк.

Каковы "определенные правила"? Вкратце, существует разумный способ для функций базового типа работать с усиленным типом таким образом, чтобы они следовали обычным правилам функциональной композиции. Например, если у вас есть функция целых чисел, скажем

int M(int x) { return x + N(x * 2); }

тогда соответствующая функция в Nullable<int> может заставить все операторы и вызовы там работать вместе "так же, как они делали это раньше.

(Это невероятно расплывчато и неточно; вы попросили объяснения, в котором ничего не говорилось о знании функциональной композиции.)

Каковы "операции"?

  1. Существует операция "unit" (иногда вызывающая путаницу, называемая операцией "return"), которая берет значение из простого типа и создает эквивалентное монадическое значение. Это, в сущности, позволяет получить значение неусиленного типа и превратить его в значение усиленного типа. Это может быть реализовано как конструктор на языке ОО.

  2. Существует операция "связывания", которая принимает монадическое значение, и функция, которая может преобразовать значение и возвращает новое монадическое значение. Связывание является ключевой операцией, которая определяет семантику монады. Это позволяет нам преобразовывать операции над неусиленным типом в операции над усиленным типом, которые подчиняются правилам функциональной композиции, упомянутым ранее.

  3. Часто есть способ вернуть неусиленный тип из усиленного. Строго говоря, для этой операции не обязательно иметь монаду. (Хотя это необходимо, если вы хотите иметь комаду. Мы не будем обсуждать это далее в этой статье.)

Снова, возьмите Nullable<T> в качестве примера. Вы можете превратить int в Nullable<int> с помощью конструктора. Компилятор С# позаботится о наиболее обнуляемом "поднятии" за вас, но если этого не произойдет, преобразование подъема будет простым: операция, скажем,

int M(int x) { whatever }

превращается в

Nullable<int> M(Nullable<int> x) 
{ 
    if (x == null) 
        return null; 
    else 
        return new Nullable<int>(whatever);
}

И превращение Nullable<int> обратно в int выполняется с помощью свойства Value.

Это функция преобразования, которая является ключевым битом. Обратите внимание, как фактическая семантика операции обнуляемости - что операция с null распространяет null - фиксируется в преобразовании. Мы можем обобщить это.

Предположим, у вас есть функция от int до int, как у нашего оригинального M Вы можете легко превратить это в функцию, которая принимает int и возвращает Nullable<int> потому что вы можете просто запустить результат через конструктор Nullable<int>. Теперь предположим, что у вас есть метод высшего порядка:

static Nullable<T> Bind<T>(Nullable<T> amplified, Func<T, Nullable<T>> func)
{
    if (amplified == null) 
        return null;
    else
        return func(amplified.Value);
}

Видишь, что ты можешь с этим сделать? Любой метод, который принимает int и возвращает int, или принимает int и возвращает Nullable<int> теперь может применять к нему семантику NULL.

Кроме того: предположим, у вас есть два метода

Nullable<int> X(int q) { ... }
Nullable<int> Y(int r) { ... }

а ты хочешь их составить

Nullable<int> Z(int s) { return X(Y(s)); }

То есть Z является композицией X и Y Но вы не можете сделать это, потому что X принимает int, а Y возвращает Nullable<int>. Но так как у вас есть операция "связать", вы можете сделать эту работу:

Nullable<int> Z(int s) { return Bind(Y(s), X); }

Операция связывания с монадой - это то, что заставляет работать функции для усиленных типов. "Правила", о которых я говорил выше, заключаются в том, что монада сохраняет правила нормальной композиции функций; что сочинение с использованием тождественных функций приводит к исходной функции, эта композиция является ассоциативной и так далее.

В С# "Bind" называется "SelectMany". Посмотрите, как это работает на монаде последовательности. Нам нужно иметь две вещи: превратить значение в последовательность и связать операции с последовательностями. В качестве бонуса у нас также есть "превратить последовательность обратно в значение". Эти операции:

static IEnumerable<T> MakeSequence<T>(T item)
{
    yield return item;
}
// Extract a value
static T First<T>(IEnumerable<T> sequence)
{
    // let just take the first one
    foreach(T item in sequence) return item; 
    throw new Exception("No first item");
}
// "Bind" is called "SelectMany"
static IEnumerable<T> SelectMany<T>(IEnumerable<T> seq, Func<T, IEnumerable<T>> func)
{
    foreach(T item in seq)
        foreach(T result in func(item))
            yield return result;            
}

Правило обнуляемой монады состояло в том, чтобы "объединить две функции, которые производят обнуляемые вместе, проверить, не приводит ли внутренняя к нулю; если это так, вывести нулевое, если нет, то вызвать внешнюю с результатом". Это желаемая семантика обнуляемый.

Правило монады последовательностей состоит в том, чтобы "объединить две функции, которые производят последовательности, применить внешнюю функцию к каждому элементу, созданному внутренней функцией, и затем объединить все полученные последовательности вместе". Основная семантика монад SelectMany методах Bind/SelectMany; это метод, который говорит вам, что на самом деле означает монада.

Мы можем сделать еще лучше. Предположим, у вас есть последовательность целых чисел и метод, который принимает целые числа и приводит к последовательностям строк. Мы могли бы обобщить операцию связывания, чтобы разрешить составление функций, которые принимают и возвращают различные усиленные типы, при условии, что входы одного совпадают с выходами другого:

static IEnumerable<U> SelectMany<T,U>(IEnumerable<T> seq, Func<T, IEnumerable<U>> func)
{
    foreach(T item in seq)
        foreach(U result in func(item))
            yield return result;            
}

Так что теперь мы можем сказать: "Амплифицировать эту группу отдельных целых чисел в последовательность целых чисел. Преобразовать это конкретное целое число в серию строк, усиленных в последовательность строк. Теперь соедините обе операции: амплифицируйте эту группу целых чисел в конкатенацию все последовательности строк. " Монады позволяют вам составлять ваши усиления.

Какую проблему он решает и какие места он использует чаще всего?

Это скорее вопрос о том, "какие проблемы решает шаблон синглтона?", Но я попробую.

Монады обычно используются для решения таких проблем, как:

  • Мне нужно создать новые возможности для этого типа и все еще комбинировать старые функции этого типа, чтобы использовать новые возможности.
  • Мне нужно захватить кучу операций над типами и представить эти операции как компонуемые объекты, создавая все большие и большие композиции, пока у меня не будет представлена только правильная серия операций, а затем мне нужно начать получать результаты из этой вещи.
  • Мне нужно четко представлять побочные операции на языке, который ненавидит побочные эффекты

С# использует монады в своем дизайне. Как уже упоминалось, обнуляемый шаблон очень похож на "возможно, монаду". LINQ полностью построен из монад; Метод SelectMany - это то, что выполняет семантическую работу над композицией операций. (Эрик Мейер любит указывать, что каждая функция LINQ может быть реализована на SelectMany; все остальное просто удобство.)

Чтобы прояснить, какое понимание я искал, скажем, вы конвертировали приложение FP с монадами в приложение ООП. Что бы вы сделали, чтобы перенести обязанности монад в приложение ООП?

Большинство языков ООП не имеют достаточно богатой системы типов, чтобы непосредственно представлять сам образец монады; вам нужна система типов, которая поддерживает типы более высоких типов, чем универсальные типы. Так что я бы не стал это делать. Скорее, я бы реализовал универсальные типы, которые представляют каждую монаду, и реализовал бы методы, которые представляют три необходимые вам операции: преобразование значения в усиленное значение, (возможно) преобразование усиленного значения в значение и преобразование функции с неусиленными значениями в функция на усиленные значения.

Хорошее место для начала - как мы реализовали LINQ в С#. SelectMany метод SelectMany; это ключ к пониманию того, как работает монада последовательностей в С#. Это очень простой метод, но очень мощный!


Предложено, дальнейшее чтение:

  1. Для более глубокого и теоретически обоснованного объяснения монад в С# я настоятельно рекомендую мою (Эрика Липперта) статью коллеги Уэса Дайера на эту тему. Эта статья и объяснила мне монады, когда они, наконец, "щелкнули" для меня.
  2. Хорошая иллюстрация того, почему вы можете захотеть монаду (использует в ней примеры Haskell).
  3. Вроде, "перевод" предыдущей статьи на JavaScript.

Ответ 2

Зачем нужны монады?

  • Мы хотим запрограммировать только с помощью функций. ( "функциональное программирование" после всех -FP).
  • Тогда у нас есть первая большая проблема. Это программа:

    f(x) = 2 * x

    g(x,y) = x / y

    Как мы можем сказать , что должно быть выполнено первым? Как мы можем сформировать упорядоченную последовательность функций (т.е. программу), используя не более функций?

    Решение: создать функции. Если вы хотите сначала g, а затем f, просто напишите f(g(x,y)). Хорошо, но...

  • Другие проблемы: некоторые функции могут выйти из строя (т.е. g(2,0), делить на 0). У нас есть no "exceptions" в FP. Как мы его решаем?

    Решение. Пусть разрешает функции возвращать два типа вещей: вместо g : Real,Real -> Real (функция из двух реалов в реальную), разрешите g : Real,Real -> Real | Nothing (функция из двух реалов в ( реальный или ничего)).

  • Но функции должны (проще) возвращать только одну вещь.

    Решение. Позвольте создать новый тип данных, которые будут возвращены, "тип типа бокса", который может быть реальным или просто ничего. Следовательно, мы можем иметь g : Real,Real -> Maybe Real. Хорошо, но...

  • Что происходит теперь с f(g(x,y))? f не готов потреблять a Maybe Real. И мы не хотим изменять каждую функцию, с которой мы могли бы соединиться с g, чтобы использовать Maybe Real.

    Решение: пусть имеет специальную функцию для "соединения" / "компоновки" / "ссылок" функций. Таким образом, мы можем, за кулисами, адаптировать выход одной функции для подачи следующего.

    В нашем случае: g >>= f (connect/compose g to f). Мы хотим, чтобы >>= получал вывод g, проверял его, и в случае, если он Nothing просто не вызывает f и возвращает Nothing; или наоборот, извлеките в коробку Real и подайте f с ним. (Этот алгоритм - это просто реализация >>= для типа Maybe).

  • Возникают многие другие проблемы, которые могут быть решены с использованием этого же шаблона: 1. Используйте "поле" для кодирования/хранения различных значений/значений и иметь такие функции, как g, которые возвращают эти "значения в коробке". 2. Имейте композиторы/компоновщики g >>= f, чтобы помочь подключить вывод g к вводу f, поэтому нам не нужно вообще менять f.

  • Замечательные проблемы, которые могут быть решены с помощью этого метода:

    • имеющий глобальное состояние, которое может использовать каждая функция в последовательности функций ( "программа" ): solution StateMonad.

    • Нам не нравятся "нечистые функции": функции, которые дают разные выходные данные для одного входа. Поэтому отметьте эти функции, чтобы они вернули значение с тегами/коробкой: IO monad.

Общее счастье!!!!

Ответ 3

Я бы сказал, что ближайшая аналогия с монадами - это " шаблон команды ".

В шаблоне команды вы помещаете обычное выражение или выражение в объект команды. Объект команды предоставляет метод execute, который выполняет упакованный оператор. Таким образом, оператор превращается в объекты первого класса, которые могут передаваться и выполняться по желанию. Команды могут быть составлены так, что вы можете создать программный объект путем объединения и вложения командных объектов.

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

Шаблон команды может использоваться для добавления (или удаления) языковых функций, которые не поддерживаются языком хоста. Например, в гипотетическом языке ОО без исключений вы можете добавить семантику исключений, предоставляя командам методы try и throw. Когда команда вызывает throw, вызывающий возвращается к списку (или дереву) команд до последнего вызова "try". И наоборот, вы можете удалить семантику исключений из языка (если вы считаете, что исключения плохие), перехватывая все исключения, выдаваемые каждой отдельной командой, и превращая их в коды ошибок, которые затем передаются следующей команде.

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

Теперь в действительности шаблоны команд не используются в качестве общей языковой функции, подобной этой. Затраты на превращение каждого оператора в отдельный класс приведут к невыносимому количеству шаблонного кода. Но в принципе его можно использовать для решения тех же проблем, что и монады для решения в фп.

Ответ 4

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

Какая проблема решает и что являются наиболее распространенными местами, которые он использовал? являются наиболее распространенными местами, которые он использовал?

В терминах программирования OO монада представляет собой интерфейс (или, скорее всего, mixin), параметризованный типом, двумя способами: return и bind, которые описывают:

  • Как ввести значение, чтобы получить монадическое значение этой введенной ценности тип;
  • Как использовать функцию, которая делает монадическое значение из не монадический, по монадическому значению.

Проблема, которую он решает, - это тот же тип проблем, который вы ожидаете от любого интерфейса, а именно: "У меня есть куча разных классов, которые делают разные вещи, но, похоже, делают эти разные вещи таким образом, что имеет базовое сходство. Как я могу описать это сходство между ними, даже если сами классы не являются действительно подтипами чего-либо ближе, чем к классу" Объект "?"

Более конкретно, интерфейс Monad "похож на IEnumerator или IIterator тем, что он принимает тип, который сам берет тип. Основная" точка "Monad, хотя и способна связывать операции, основанные на внутреннем типе, даже с точки зрения наличия нового" внутреннего типа", сохраняя или даже усиливая информационную структуру основного класса.

Ответ 5

У вас есть недавняя презентация Кристофер Лиги " Monadologie - профессиональная помощь по типу тревожности " (12 июля 2010 г.), которая довольно интересна по темам продолжения и монады.
Видео с этой (слайд-шоу) презентацией действительно доступно на vimeo.
Эта монадная часть начинается примерно через 37 минут в этом часовом видео и начинается со слайда 42 из 58 презентаций слайдов.

Он представлен как "ведущий шаблон проектирования для функционального программирования", но в примерах используется язык Scala, который является одновременно ООП и функциональным.
Вы можете прочитать больше о Monad в Scala в сообщении в блоге " Monads - еще один способ абстрагировать вычисления в Scala ", от Debasish Ghosh (27 марта 2008 г.).

Конструктор типа M является монадой, если он поддерживает следующие операции:

# the return function
def unit[A] (x: A): M[A]

# called "bind" in Haskell 
def flatMap[A,B] (m: M[A]) (f: A => M[B]): M[B]

# Other two can be written in term of the first two:

def map[A,B] (m: M[A]) (f: A => B): M[B] =
  flatMap(m){ x => unit(f(x)) }

def andThen[A,B] (ma: M[A]) (mb: M[B]): M[B] =
  flatMap(ma){ x => mb }

Так, например (в Scala):

  • Option монада
    def unit[A] (x: A): Option[A] = Some(x)

    def flatMap[A,B](m:Option[A])(f:A =>Option[B]): Option[B] =
      m match {
       case None => None
       case Some(x) => f(x)
      }
  • List Монада
    def unit[A] (x: A): List[A] = List(x)

    def flatMap[A,B](m:List[A])(f:A =>List[B]): List[B] =
      m match {
        case Nil => Nil
        case x::xs => f(x) ::: flatMap(xs)(f)
      }

Monad имеет большое значение в Scala из-за удобного синтаксиса, созданного для использования преимуществ структур Monad:

for понимания в Scala:

for {
  i <- 1 to 4
  j <- 1 to i
  k <- 1 to j
} yield i*j*k

переводится компилятором в:

(1 to 4).flatMap { i =>
  (1 to i).flatMap { j =>
    (1 to j).map { k =>
      i*j*k }}}

Ключевая абстракция - это flatMap, который связывает вычисления через цепочку.
Каждый вызов flatMap возвращает один и тот же тип структуры данных (но с разным значением), который служит входом для следующей команды в цепочке.

В приведенном выше фрагменте flatMap принимает в качестве входных данных замыкание (SomeType) => List[AnotherType] и возвращает List[AnotherType]. Важно отметить, что все flatMaps принимают тот же тип замыкания, что и ввод, и возвращают тот же тип, что и вывод.

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


Если вы берете две операции (которые могут потерпеть неудачу) и передаете результат третьей, например:

lookupVenue: String => Option[Venue]
getLoggedInUser: SessionID => Option[User]
reserveTable: (Venue, User) => Option[ConfNo]

но, не используя Monad, вы получаете запутанный ООП-код, такой как:

val user = getLoggedInUser(session)
val confirm =
  if(!user.isDefined) None
  else lookupVenue(name) match {
    case None => None
    case Some(venue) =>
      val confno = reserveTable(venue, user.get)
      if(confno.isDefined)
        mailTo(confno.get, user.get)
      confno
  }

в то время как с Monad вы можете работать с реальными типами (Venue, User), как и все операции, и сохранять скрытые данные проверки Option, все из-за плоских карт синтаксиса for:

val confirm = for {
  venue <- lookupVenue(name)
  user <- getLoggedInUser(session)
  confno <- reserveTable(venue, user)
} yield {
  mailTo(confno, user)
  confno
}

Часть yield будет выполняться, только если все три функции имеют Some[X]; любой None будет непосредственно возвращен для confirm.


Так:

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

И самая большая сила приходит с возможностью составлять монады, которые служат различным целям, в расширяемые абстракции в приложении.

Это упорядочение и создание потоков действий монадой осуществляется языковым компилятором, который выполняет преобразование посредством магии замыканий.


Кстати, Monad - это не только модель вычислений, используемая в FP:

Теория категорий предлагает множество моделей вычислений. Среди них

  • стрелочная модель вычислений
  • модель вычислений Monad
  • Аппликативная модель расчетов

Ответ 6

Я написал небольшую статью, в которой сравнивался стандартный код OOP Python с монадическим кодом Python, демонстрирующий базовый вычислительный процесс с помощью диаграмм. Это не предполагает никаких предварительных знаний о ФП. Надеюсь, вы найдете это полезным - http://nikgrozev.com/2013/12/10/monads-in-15-minutes/

Ответ 7

Чтобы уважать быстрых читателей, сначала я начинаю с точного определения, продолжайте с более подробным объяснением "простого английского", а затем перейдите к примерам.

Вот краткое и точное определение, слегка измененное:

A monad (в информатике) формально является картой, которая:

  • отправляет каждый тип X некоторого заданного языка программирования на новый тип T(X) (называемый "тип T -компьютеров со значениями в X" );

  • снабженное правилом для составления двух функций вида f:X->T(Y) и g:Y->T(Z) к функции g∘f:X->T(Z);

  • таким образом, который ассоциативен в очевидном смысле и унитален по отношению к данной единичной функции, называемой pure_X:X->T(X), считая, что она принимает значение чистому вычислению, которое просто возвращает это значение.

Итак, в простых словах, monad является правилом перехода от любого типа X к другому типу T(X), а правило для перехода от двух функций f:X->T(Y) и g:Y->T(Z) (который вы хотите создать, но не можете) к новой функции h:X->T(Z). Что, однако, не является композицией в строгом математическом смысле. Мы в основном "сгибаем" функцию композиции или переопределяем, как скомпилированы функции.

Плюс, мы требуем, чтобы правило монады сочинения удовлетворяло "очевидным" математическим аксиомам:

  • Ассоциативность. Составление f с помощью g, а затем с h (извне) должно быть таким же, как и составление g с h, а затем с f ( изнутри).
  • Свойство Unital. Составление f с помощью функции тождества с каждой стороны должно давать f.

Опять же, простыми словами, мы не можем просто сходить с ума, переопределяя нашу функциональную композицию по своему усмотрению:

  • Сначала нам нужна ассоциативность, чтобы иметь возможность составлять несколько функций в строке, например. f(g(h(k(x))), и не нужно беспокоиться о том, чтобы указать пары функций, составляющих порядок. Поскольку правило монады только предписывает, как составить пару функций, без этой аксиомы, нам нужно было бы знать, какая пара состоит в первую очередь и так далее. (Обратите внимание, что это отличается от свойства коммутативности, которое f, составленное с помощью g, было таким же, как g, составленным с помощью f, что не требуется).
  • И во-вторых, нам нужно унитальное свойство, которое просто означает, что идентичности составляют тривиально так, как мы их ожидаем. Таким образом, мы можем безопасно выполнять функции рефакторинга, когда эти идентификаторы могут быть извлечены.

Итак, вкратце: монада - это правило расширения типа и составных функций, удовлетворяющих двум аксиомам - ассоциативности и унитального свойства.

В практическом плане вы хотите, чтобы монада была реализована для вас языком, компилятором или фреймворком, который позаботился бы о создании для вас функций. Поэтому вы можете сосредоточиться на написании логики функций, а не на том, как их выполнение выполняется.

Это по существу это, в двух словах.


Будучи профессиональным математиком, я предпочитаю не называть h "композицию" f и g. Потому что математически это не так. Призывая его к "композиции" неправильно, предполагается, что h является истинной математической композицией, которой она не является. Он даже не определяется однозначно f и g. Вместо этого, это результат нашей монады нового "правила сочинения" функций. Что может быть полностью отличным от фактического математического состава, даже если последнее существует!


Монада не является функтором! Функтором f является правило перехода от типа X к типу F(X) и функциям (морфизму) между типами X и Y к функциям между F(X) и F(Y) (отправка объектов объектам и их морфизмы к морфизмам в теории категорий). Вместо этого монада отправляет пара функций f и g в новую h.


Чтобы сделать его менее сухим, позвольте мне проиллюстрировать его на примере что я аннотируюсь небольшими разделами, поэтому вы можете пропустить вправо.

Выброс исключений в качестве примеров Monad

Предположим, что мы хотим создать две функции:

f: x -> 1 / x
g: y -> 2 * y

Но f(0) не определен, поэтому генерируется исключение e. Тогда как вы можете определить композиционное значение g(f(0))? Конечно, выбросьте исключение! Может быть, тот же e. Возможно, новое обновленное исключение e1.

Что именно происходит здесь? Во-первых, нам нужны новые значения (-и) исключения (разные или одинаковые). Вы можете назвать их nothing или null или что-то еще, но суть остается прежней - они должны быть новыми значениями, например. он не должен быть number в нашем примере здесь. Я предпочитаю не называть их null, чтобы избежать путаницы с тем, как null может быть реализован на любом конкретном языке. В равной степени я предпочитаю избегать nothing, потому что он часто ассоциируется с null, что в принципе является тем, что должен делать null, этот принцип часто сгибается по практическим соображениям.

Что такое исключение?

Это тривиальный вопрос для любого опытного программиста, но я хотел бы оставить несколько слов, чтобы потушить любой червь путаницы:

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

Это может варьироваться от удаления любых деталей и возврата одного глобального значения (например, NaN или null)) или создания длинного списка журналов или того, что именно произошло, отправки его в базу данных и репликации по всем распределенным данным уровень хранения;)

Важное различие между этими двумя крайними примерами исключения заключается в том, что в первом случае нет побочных эффектов. Во втором есть. Это подводит нас к вопросу (тыс. Долл.):

Разрешены ли исключения в чистых функциях?

Короткий ответ: Да, но только тогда, когда они не приводят к побочным эффектам.

Более длинный ответ. Чтобы быть чистым, ваш выход функции должен быть однозначно определен его вводом. Поэтому мы корректируем нашу функцию f, отправив 0 в новое абстрактное значение e, которое мы называем исключением. Мы гарантируем, что значение e не содержит внешней информации, которая не определяется однозначно нашим входом, который равен X. Итак, вот пример исключения без побочного эффекта:

e = {
  type: error, 
  message: 'I got error trying to divide 1 by 0'
}

И вот один с побочным эффектом:

e = {
  type: error, 
  message: 'Our committee to decide what is 1/0 is currently away'
}

На самом деле, он имеет только побочные эффекты, если это сообщение может измениться в будущем. Но если это никогда не будет изменено, это значение станет однозначно предсказуемым, и поэтому побочного эффекта не будет.

Сделать это еще глупее. Функция, возвращающая 42 когда-либо, явно чиста. Но если кто-то сумасшедший решает сделать 42 переменную, значение которой может измениться, сама же функция перестает быть чистой в новых условиях.

Обратите внимание, что я использую объектную литературу для простоты, чтобы продемонстрировать суть. К сожалению, вещи перепутаны с такими языками, как JavaScript, где error - это не тот тип, который ведет себя так, как мы хотим здесь, в отношении композиции функций, тогда как фактические типы типа null или NaN ведут себя не так, а скорее пройти через некоторые искусственные, а не всегда интуитивные преобразования типов.

Расширение типа

Поскольку мы хотим изменить сообщение внутри нашего исключения, мы действительно объявляем новый тип e для всего объекта исключения, а затем Это то, что maybe number делает, помимо своего запутанного имени, которое должно быть либо типа number, либо нового типа исключения e, так что это действительно объединение number | E number и e. В частности, это зависит от того, как мы хотим построить e, который не предлагается и не отображается в имени maybe number.

Что такое функциональная композиция?

Это математическая операция, выполняющая функции f: X -> Y и g: Y -> Z и построение их состав как функция h: X -> Z, удовлетворяющий h(x) = g(f(x)). Проблема с этим определением возникает, когда результат F(X) не разрешен как аргумент g.

В математике эти функции не могут быть составлены без дополнительной работы. Строго математическое решение для нашего предыдущего примера f и g заключается в удалении 0 из набора определений f. С этим новым набором определений (новый более ограничительный тип X), f становится составным с g.

Однако в программировании не очень практично ограничивать набор определений f таким образом. Вместо этого могут использоваться исключения.

Или как другой подход, искусственные значения создаются как NaN, undefined, null, Infinity и т.д. Итак, вы оцениваете 1/0 до Infinity и 1/-0 до -Infinity. И затем принудительно добавьте новое значение в ваше выражение вместо исключения исключения. Приведя результаты, вы можете или не можете найти предсказуемое:

1/0                // => Infinity
parseInt(Infinity) // => NaN
NaN < 0            // => false
false + 1          // => 1

И мы вернемся к регулярным числам, готовым двигаться дальше;)

JavaScript позволяет нам продолжать выполнять численные выражения любой ценой без ошибок, как в приведенном выше примере. Это означает, что это также позволяет составлять функции. Именно в этом заключается монада - правило состоит в том, чтобы составлять функции, удовлетворяющие аксиомам, как определено в начале этого ответа.

Но является ли правило составной функции, связанной с реализацией JavaScript для обработки числовых ошибок, монадой?

Чтобы ответить на этот вопрос, вам нужно только проверить аксиомы (слева как упражнение, как не часть вопроса здесь;).

Можно ли использовать исключение throw для построения монады?

Действительно, более полезная монада вместо этого была бы предписанием правила что если f выдает исключение для некоторого X, то и его композиция с любым g. Плюс сделать исключение e глобально уникальным только с одним возможным значением (терминальный объект в теории категорий). Теперь две аксиомы мгновенно проверяются, и мы получаем очень полезную монаду. И результат - это то, что хорошо известно как возможно, монада.

Ответ 8

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

  • return x создает значение типа монады, которое инкапсулирует x
  • m >>= f (читается как "оператор связывания") применяет функцию f к значению в монаде m

Вот что такое монада. Есть еще несколько технических особенностей, но в основном эти две операции определяют монаду. Реальный вопрос: "Что делает монада?", И это зависит от монада - списки - это монады, Maybes - это монады, операции ввода-вывода - это монады. Все, что это означает, когда мы говорим, что это монады, это то, что они имеют интерфейс return монад и >>=.

Ответ 9

От wikipedia:

В функциональном программировании монада своего рода абстрактный тип данных, используемый для представляют вычисления (вместо данные в модели домена). Монады позволить программисту цепочки действий вместе построить трубопровод, в котором каждое действие украшено предусмотрены дополнительные правила обработки Монадой. Программы, написанные на функциональный стиль может использовать монады для структурирования процедур, которые включают операции секвенирования, 1 [2] или для определения произвольных потоков управления (например, обработка concurrency, продолжениями или исключениями).

Формально монада строится по определение двух операций (связывание и return) и конструктор типов M, который должен выполнить несколько свойств для разрешить правильный состав монадические функции (т.е. функции, использовать значения из монады как их аргументы). Операция возврата принимает значение из простого типа и помещает его в монадический контейнер типа М. Операция связывания выполняет обратный процесс, извлечение первоначальное значение из контейнера и передавая его следующему функции в конвейере.

Программист будет составлять монадический функции для определения обработки данных трубопровод. Монада действует как рамки, поскольку это многоразовое поведение который определяет порядок, в котором конкретных монадических функций в трубопровод вызываются и управляют всеми тайная работа, требуемая вычисление. [3] Связывание и возвращение операторы чередуются в трубопроводе будет выполняться после каждого монадического функция возвращает управление, и позаботиться о конкретных аспектах обрабатывается монадой.

Я считаю, что это очень хорошо объясняет.

Ответ 10

Я попытаюсь сделать кратчайшее определение, которое я могу использовать, используя условия ООП:

Общий класс CMonadic<T> является монадой, если он определяет по крайней мере следующие методы:

class CMonadic<T> { 
    static CMonadic<T> create(T t);  // a.k.a., "return" in Haskell
    public CMonadic<U> flatMap<U>(Func<T, CMonadic<U>> f); // a.k.a. "bind" in Haskell
}

и если для всех типов T применяются следующие законы и их возможные значения t

слева:

CMonadic<T>.create(t).flatMap(f) == f(t)

правильный тождество

instance.flatMap(CMonadic<T>.create) == instance

ассоциативность:

instance.flatMap(f).flatMap(g) == instance.flatMap(t => f(t).flatMap(g))

Примеры:

Список монады может иметь:

List<int>.create(1) --> [1]

И flatMap в списке [1,2,3] может работать так:

intList.flatMap(x => List<int>.makeFromTwoItems(x, x*10)) --> [1,10,2,20,3,30]

Итераторы и наблюдатели также могут быть сделаны монадическими, а также Promises и Задачи.

Комментарий

Монады не так уж сложны. Функция flatMap очень похожа на более часто встречающуюся map. Он получает аргумент функции (также известный как делегат), который он может вызывать (немедленно или позже, ноль или более) со значением, исходящим из универсального класса. Он ожидает, что переданная функция также вернет возвращаемое значение в том же родовом классе. Чтобы помочь с этим, он предоставляет create, конструктор, который может создать экземпляр этого универсального класса из значения. Результат возврата flatMap также является общим классом того же типа, который часто содержит те же значения, которые содержатся в результатах возврата одного или нескольких приложений flatMap для ранее сохраненных значений. Это позволяет вам цепочки flatMap столько, сколько вы хотите:

intList.flatMap(x => List<int>.makeFromTwo(x, x*10))
       .flatMap(x => x % 3 == 0 
                   ? List<string>.create("x = " + x.toString()) 
                   : List<string>.empty())

Как раз так бывает, что этот родовой класс полезен в качестве базовой модели для огромного количества вещей. Это (вместе с теорией теории жаргонизма) является причиной того, почему Монады кажутся настолько трудными для понимания или объяснения. Они очень абстрактны и становятся полезными только после того, как они специализируются.

Например, вы можете моделировать исключения, используя монадические контейнеры. Каждый контейнер будет либо содержать результат операции, либо возникшую ошибку. Следующая функция (делегат) в цепочке обратных вызовов flatMap будет вызываться только в том случае, если предыдущий упаковал значение в контейнер. В противном случае, если ошибка была упакована, ошибка будет продолжать распространяться через скованные контейнеры до тех пор, пока не будет найден контейнер, у которого есть функция обработчика ошибок, связанная с помощью метода с именем .orElse() (такой метод будет разрешенным расширением)

Примечания. Функциональные языки позволяют писать функции, которые могут работать на любом виде монодического общего класса. Чтобы это сработало, нужно было бы написать общий интерфейс для монад. Я не знаю, возможно ли написать такой интерфейс в С#, но насколько я знаю, это не так:

interface IMonad<T> { 
    static IMonad<T> create(T t); // not allowed
    public IMonad<U> flatMap<U>(Func<T, IMonad<U>> f); // not specific enough,
    // because the function must return the same kind of monad, not just any monad
}

Ответ 11

Независимо от того, имеет ли монада "естественная" интерпретация в ОО, зависит от монады. На языке, таком как Java, вы можете перевести, возможно, монаду на язык проверки нулевых указателей, так что вычисления, которые не выполняются (т.е. Производят Nothing в Haskell), вызывают нулевые указатели в качестве результатов. Вы можете перевести государственную монаду в язык, сгенерированный с помощью создания изменяемой переменной и методов для изменения ее состояния.

Монада является моноидом в категории эндофенторов.

Информация, содержащаяся в этом предложении, очень глубока. И вы работаете в монаде с любым императивным языком. Монада - это "секвенированный" доменный язык. Он удовлетворяет некоторым интересным свойствам, которые вместе образуют монаду как математическую модель "императивного программирования". Haskell упрощает определение небольших (или больших) императивных языков, которые можно комбинировать различными способами.

Как программист OO, вы используете иерархию классов языков для организации видов функций или процедур, которые могут быть вызваны в контексте, что вы называете объектом. Монада также является абстракцией по этой идее, поскольку разные монады можно объединить произвольными способами, эффективно "импортируя" все субмонадные методы в область.

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

Для этой цели можно использовать монадные трансформаторы, и есть высококачественная коллекция всех "стандартных" монад:

  • Списки (недетерминированные вычисления, обрабатывая список как домен)
  • Возможно (вычисления, которые могут завершиться неудачно, но для которых отчетность несущественна)
  • Ошибка (вычисления, которые могут завершиться с ошибкой и требуют обработки исключений
  • Reader (вычисления, которые могут быть представлены композициями простых функций Haskell)
  • Writer (вычисления с последовательным "рендерингом" / "протоколированием" (для строк, html и т.д.)
  • Cont (продолжения)
  • IO (вычисления, которые зависят от базовой компьютерной системы)
  • Состояние (вычисления, контекст которых содержит изменяемое значение)

с соответствующими монад-трансформаторами и типами классов. Классы типов позволяют дополнять подход к объединению монад путем объединения их интерфейсов, так что конкретные монады могут реализовать стандартный интерфейс для монады "вид". Например, модуль Control.Monad.State содержит класс MonadState s m и (State s) является экземпляром формы

instance MonadState s (State s) where
    put = ...
    get = ...

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

Итак:

return :: a -> m a

- это функция, которая вводит значение типа a в монадное "действие" типа m a.

(>>=) :: m a -> (a -> m b) -> m b

- это функция, которая принимает действие монады, оценивает ее результат и применяет функцию к результату. Опытная вещь ( → =) заключается в том, что результат находится в одной монаде. Другими словами, в m → = f, ( → =) вытягивает результат из m и связывает его с f, чтобы результат был в монаде. (В качестве альтернативы можно сказать, что ( → =) тянет f в m и применяет его к результату.) Как следствие, если мы имеем f:: a → mb и g:: b → mc, мы можем Действия "последовательности":

m >>= f >>= g

Или, используя "do notation"

do x <- m
   y <- f x
   g y

Тип для ( → ) может быть освещен.

(>>) :: m a -> m b -> m b

Он соответствует оператору (;) в процедурных языках, например C. Он позволяет обозначать как:

m = do x <- someQuery
       someAction x
       theNextAction
       andSoOn

В математической и философской логике у нас есть рамки и модели, которые "естественно" моделируются с монадизмом. Интерпретация - это функция, которая изучает модельную область и вычисляет значение истинности (или обобщений) предложения (или формулы при обобщениях). В модальной логике по необходимости мы можем сказать, что предложение необходимо, если оно истинно в "всевозможном мире" - если оно верно в отношении любой допустимой области. Это означает, что модель на языке для предложения может быть подтверждена как модель, домен которой состоит из набора отдельных моделей (один соответствует каждому возможному миру). Каждая монада имеет метод "join", который выравнивает слои, что подразумевает, что каждое монадское действие, результат которого является действием монады, может быть встроено в монаду.

join :: m (m a) -> m a

Что еще более важно, это означает, что монада закрыта под действием "укладки слоев". Вот как работают монадные трансформаторы: они объединяют монады, предоставляя методы "join-like" для таких типов, как

newtype MaybeT m a = MaybeT { runMaybeT :: m (Maybe a) }

чтобы мы могли преобразовать действие в (MaybeT m) в действие в m, эффективно свертывая слои. В этом случае runMaybeT:: MaybeT m a → m (Возможно, a) - наш метод соединения. (MaybeT m) является монадой, а MaybeT:: m (Может быть, a) → MaybeT m a является конструктором нового типа действия монады в m.

Свободная монада для функтора - это монада, сгенерированная путем укладки f, с импликацией, что каждая последовательность конструкторов для f является элементом свободной монады (или, точнее, с той же формой, что и дерево последовательностей конструкторов для f). Свободные монады - полезный метод построения гибких монадов с минимальным количеством котельной плиты. В программе Haskell я мог бы использовать свободные монады для определения простых монад для "системного программирования высокого уровня", чтобы помочь поддерживать безопасность типов (я просто использую типы и их объявления). Реализации прямолинейны с использованием комбинаторов):

data RandomF r a = GetRandom (r -> a) deriving Functor
type Random r a = Free (RandomF r) a


type RandomT m a = Random (m a) (m a) -- model randomness in a monad by computing random monad elements.
getRandom     :: Random r r
runRandomIO   :: Random r a -> IO a (use some kind of IO-based backend to run)
runRandomIO'  :: Random r a -> IO a (use some other kind of IO-based backend)
runRandomList :: Random r a -> [a]  (some kind of list-based backend (for pseudo-randoms))

Монадизм - это базовая архитектура для того, что вы могли бы назвать шаблоном "интерпретатор" или "команда", абстрагированным до самой ясной формы, поскольку каждое монадическое вычисление должно быть "запущено", по крайней мере тривиально. (Система времени выполнения запускает для нас монаду IO и является точкой входа в любую программу Haskell. IO "управляет" остальными вычислениями, выполняя действия IO в порядке).

Тип соединения также заключается в том, что мы получаем утверждение о том, что монада является моноидом в категории endofunctors. Присоединение обычно более важно для теоретических целей в силу его типа. Но понимание типа означает понимание монадов. Присоединительные и монадные трансформаторные типы типа соединения - это фактически композиции эндофенторов в смысле функционального состава. Чтобы поместить его в псевдоязык, похожий на Haskell,

Foo:: m (m a) ↔ (m. m) a

Ответ 12

Монада - это массив функций

(Pst: массив функций - это просто вычисление).

Собственно, вместо истинного массива (одна функция в одном массиве ячеек) у вас есть те функции, прикованные другой функцией → =. → = позволяет адаптировать результаты функции я к функции подачи я + 1, выполнять вычисления между ними или, даже, не вызывать функцию я + 1.

Используемые здесь типы "типы с контекстом". Это значение с тегом. Привязанные функции должны принимать "голые значения" и возвращать отмеченный результат. Одна из обязанностей → = заключается в извлечении голого значения из его контекста. Существует также функция "return", которая принимает голые значения и помещает их в тег.

Пример с Maybe. Пусть он используется для хранения простого целого числа, на котором выполняются вычисления.

-- a * b
multiply :: Int -> Int -> Maybe Int
multiply a b = return  (a*b)

-- divideBy 5 100 = 100 / 5
divideBy :: Int -> Int -> Maybe Int
divideBy 0 _ = Nothing -- dividing by 0 gives NOTHING
divideBy denom num = return (quot num denom) -- quotient of num / denom

-- tagged value
val1 = Just 160 

-- array of functions feeded with val1
array1 = val1 >>= divideBy 2  >>= multiply 3 >>= divideBy  4 >>= multiply 3

-- array of funcionts created with the do notation
-- equals array1 but for the feeded val1
array2 :: Int -> Maybe Int
array2 n = do
       v <- divideBy 2  n
       v <- multiply 3 v
       v <- divideBy 4 v
       v <- multiply 3 v
       return v

-- array of functions, 
-- the first >>= performs 160 / 0, returning Nothing
-- the second >>= has to perform Nothing >>= multiply 3 ....
-- and simply returns Nothing without calling multiply 3 ....
array3 = val1 >>= divideBy 0  >>= multiply 3 >>= divideBy  4 >>= multiply 3

main = do
     print array1
     print (array2 160)
     print array3

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

type MyMonad = [Int -> Maybe Int] -- my monad as a real array of functions

myArray1 = [divideBy 2, multiply 3, divideBy 4, multiply 3]

-- function for the machinery of executing each function i with the result provided by function i-1
runMyMonad :: Maybe Int -> MyMonad -> Maybe Int
runMyMonad val [] = val
runMyMonad Nothing _ = Nothing
runMyMonad (Just val) (f:fs) = runMyMonad (f val) fs

И он будет использоваться следующим образом:

print (runMyMonad (Just 160) myArray1)

Ответ 13

Монады в типичном использовании являются функциональным эквивалентом механизмов обработки процедур процедурного программирования.

В современных процедурных языках вы помещаете обработчик исключений вокруг последовательности операторов, любой из которых может вызывать исключение. Если какое-либо из операторов выдает исключение, нормальное выполнение последовательности операторов приостанавливается и переносится в обработчик исключений.

Функциональные языки программирования, однако, философски избегают функций обработки исключений из-за "goto", как их характера. Перспектива функционального программирования заключается в том, что функции не должны иметь побочных эффектов, таких как исключения, которые нарушают программный поток.

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

Поток управления сохраняется, но неожиданное событие безопасно инкапсулируется и обрабатывается.

Ответ 14

Простое объяснение Monads с примером использования Marvel здесь.

Монады - это абстракции, используемые для последовательного выполнения зависимых функций. Эффект здесь означает, что они возвращают тип в форме F [A], например Option [A], где Option - это F, называемый конструктором типов. Давайте посмотрим на это в 2 простых шага

  1. Ниже функция композиции является переходной. Таким образом, чтобы перейти от A к CI, можно составить A => B и B => C.
 A => C   =   A => B  andThen  B => C

enter image description here

  1. Однако, если функция возвращает тип эффекта, такой как Option [A], то есть A => F [B], композиция не работает, так как для перехода к B нам нужно A => B, но у нас есть A => F [B].
    enter image description here

    Нам нужен специальный оператор "bind", который знает, как объединить эти функции, которые возвращают F [A].

 A => F[C]   =   A => F[B]  bind  B => F[C]

Функция "связать" определена для конкретного F.

Существует также "return" типа A => F [A] для любого A, определенного для этого конкретного F. Чтобы быть Монадой, F должен иметь эти две функции, определенные для нее.

Таким образом, мы можем построить эффективную функцию A => F [B] из любой чистой функции A => B,

 A => F[B]   =   A => B  andThen  return

но данный F также может определять свои собственные непрозрачные "встроенные" специальные функции таких типов, которые пользователь не может определить сам (на чистом языке), например

  • "Случайный" (Range => Random [Int])
  • "печать" (String => IO [()])
  • "попробуй... поймай" и т.д.

Ответ 16

Посмотрите мой ответ на "Что такое монада?"

Он начинается с мотивирующего примера, работает через пример, выводит пример монады и формально определяет "монаду".

Он не знает знаний о функциональном программировании и использует псевдокод с синтаксисом function(argument) := expression с простейшими возможными выражениями.

Эта программа на С++ представляет собой реализацию псевдокодной монады. (Для справки: M - это конструктор типа, feed - операция привязки, а wrap - операция "return".)

#include <iostream>
#include <string>

template <class A> class M
{
public:
    A val;
    std::string messages;
};

template <class A, class B>
M<B> feed(M<B> (*f)(A), M<A> x)
{
    M<B> m = f(x.val);
    m.messages = x.messages + m.messages;
    return m;
}

template <class A>
M<A> wrap(A x)
{
    M<A> m;
    m.val = x;
    m.messages = "";
    return m;
}

class T {};
class U {};
class V {};

M<U> g(V x)
{
    M<U> m;
    m.messages = "called g.\n";
    return m;
}

M<T> f(U x)
{
    M<T> m;
    m.messages = "called f.\n";
    return m;
}

int main()
{
    V x;
    M<T> m = feed(f, feed(g, wrap(x)));
    std::cout << m.messages;
}

Ответ 17

В терминах OO монада - это свободный контейнер.

Минимальным требованием является определение class <A> Something, которое поддерживает конструктор Something(A a) и по крайней мере один метод Something<B> flatMap(Function<A, Something<B>>)

Возможно, также учитывается, имеет ли ваш класс monad какие-либо методы с сигнатурой Something<B> work(), которая сохраняет правила класса - компилятор печет в flatMap во время компиляции.

Почему монада полезна? Потому что это контейнер, который позволяет цепочки операций, которые сохраняют семантику. Например, Optional<?> сохраняет семантику isPresent для Optional<String>, Optional<Integer>, Optional<MyClass> и т.д.

Как пример,

Something<Integer> i = new Something("a")
  .flatMap(doOneThing)
  .flatMap(doAnother)
  .flatMap(toInt)

Обратите внимание, что мы начинаем с строки и заканчиваем целым числом. Довольно круто.

В OO это может немного помахать рукой, но любой метод Something, который возвращает другой подкласс Something, отвечает критерию контейнерной функции, возвращающему контейнер исходного типа.

То, как вы сохраняете семантику - то есть значение и операции контейнера не изменяются, они просто обертывают и улучшают объект внутри контейнера.

Ответ 18

С практической точки зрения (резюмируя сказанное во многих предыдущих ответах и ​​связанных статьях), мне кажется, что одна из основных "целей" (или полезности) монады заключается в том, чтобы использовать зависимости, неявные в рекурсивные вызовы метода как составная функция (т.е. когда f1 вызывает f2 вызовы f3, f3 необходимо оценить до f2 перед f1), чтобы представлять последовательный состав естественным образом, особенно в контексте ленивой модели оценки (то есть последовательной композиции как простая последовательность, например "f3(); f2(); f1();" в C - трюк особенно очевиден, если вы думаете о случае, когда f3, f2 и f1 фактически ничего не возвращают [их цепочка как f1 (f2 ( f3)) является искусственным, чисто предназначенным для создания последовательности]).

Это особенно актуально, когда задействованы побочные эффекты, т.е. когда какое-то состояние изменяется (если f1, f2, f3 не имеет побочных эффектов, неважно, в каком порядке они оцениваются, что отлично свойство чистых функциональных языков, чтобы иметь возможность распараллеливать эти вычисления, например). Чем чище функции, тем лучше.

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

Это только один аспект, как предупреждал здесь.

Ответ 19

Я делюсь своим пониманием монад, которые теоретически не могут быть идеальными. Монады о распространении контекста. Монадой является то, что вы определяете некоторый контекст для некоторых данных, а затем определяете, как контекст будет переноситься с данными по всему конвейеру обработки. И определение распространения контекста в основном сводится к определению того, как объединять несколько контекстов. Это также гарантирует, что контексты не могут быть случайно удалены из данных. Эта простая концепция может быть использована для обеспечения правильности времени компиляции программы.

Ответ 20

С точки зрения того, что программист ООП понимает (без какой-либо функциональной основы программирования), что такое монада?

Нет такого объяснения, о котором я знаю, и предположения о том, что он существует, противоречит высокомерию программистов ООП.

Какую проблему он решает и какие места он использует чаще всего?

Монады решают множество различных проблем с помощью унифицированного интерфейса, поэтому они полезны. Главное, для чего они используются - это лень - они допускают ленивые функции с побочными эффектами, что важно для FFI и тому подобного. Они также обычно используются для обработки ошибок и анализа.

Чтобы прояснить, какое понимание я искал, скажем, вы конвертировали приложение FP с монадами в приложение ООП. Что бы вы сделали, чтобы перенести обязанности монад в приложение ООП?

Так как лень редко встречается в ООП, то по большей части они не имеют значения. Однако вы можете перенести монадическую обработку ошибок.