Монада на простом английском? (Для программиста ООП без фона FP) - PullRequest
664 голосов
/ 24 апреля 2010

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

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

РЕДАКТИРОВАТЬ:

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

Ответы [ 19 ]

676 голосов
/ 24 апреля 2010

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

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

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

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

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

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

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

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

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

Что такое "операции"?

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

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

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

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

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>, потому что вы можете просто запустить результат через конструктор, допускающий значение NULL. Теперь предположим, что у вас есть метод высшего порядка:

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); }

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

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

static IEnumerable<T> MakeSequence<T>(T item)
{
    yield return item;
}
// Extract a value
static T First<T>(IEnumerable<T> sequence)
{
    // let's 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;            
}

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

Правило монады последовательностей «объединять две функции, которые создают последовательности, применять внешнюю функцию к каждому элементу, созданному внутренней функцией, и затем объединять все полученные последовательности вместе». Основная семантика монад отражена в методах 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;            
}

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

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

Это все равно, что спросить: «Какие проблемы решает синглтон-паттерн?», Но я попробую.

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

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

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

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

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

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


Предлагается, далее:

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

278 голосов
/ 25 января 2015

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

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

    f(x) = 2 * x

    g(x,y) = x / y

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

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

  3. Больше проблем: некоторые функции могут не работать (то есть g(2,0), делить на 0). У нас нет никаких «исключений» в FP . Как мы это решаем?

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

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

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

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

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

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

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

  7. Замечательные проблемы, которые можно решить с помощью этой техники:

    • с глобальным состоянием, которое может иметь каждая функция в последовательности функций («программа»): решение StateMonad.

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

полное счастье !!!!

68 голосов
/ 19 мая 2010

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

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

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

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

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

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

60 голосов
/ 17 июля 2010

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

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

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

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

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

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

41 голосов
/ 18 июля 2010

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

Он представлен как «ведущий шаблон проектирования для функционального программирования», но в примерах используется язык Scala, который является одновременно ООП и функциональным.
Вы можете прочитать больше о Monad в Scala в блоге " Monads - еще один способ абстрагировать вычисления в Scala ", от Дебашиш Гош (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)
      }

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

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:

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

  • Стрелка модели вычислений
  • Модель монады вычислений
  • Аппликативная модель расчетов
34 голосов
/ 06 июня 2014

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

26 голосов
/ 20 мая 2016

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

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

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

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

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

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

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

Кроме того, мы требуем, чтобы правило сочинения монады удовлетворяло «очевидным» математическим аксиомам:

  • Ассоциативность : Композиция f с g, а затем с h (извне) должна быть такой же, как компоновка g с h, а затем с f (с внутри).
  • Unital свойство : Компоновка f с функцией identity с обеих сторон должна дать 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.


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

Исключение как примеры монад

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

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 для обработки числовых ошибок, монадой?

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

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

Действительно, более полезной монадой было бы правило, предписывающее что если f выдает исключение для некоторого x, то же самое происходит и с любым g. Кроме того, сделайте исключение E глобально уникальным, используя только одно возможное значение ( конечный объект в теории категорий). Теперь две аксиомы мгновенно проверяются, и мы получаем очень полезную монаду. В результате получается так называемая , может быть, монада .

25 голосов
/ 24 апреля 2010

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

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

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

11 голосов
/ 26 декабря 2013

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

Универсальный класс 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 может иметь:

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

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

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

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

Комментарий

Монады не так сложны. Функция 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() (такой метод будет разрешенным расширением)

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

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 голосов
/ 24 апреля 2010

Из Википедия :

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

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

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

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

...