stringtranslate.com

Монада (функциональное программирование)

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

И понятие монады, и сам термин изначально пришли из теории категорий , где монада определяется как функтор с дополнительной структурой. [a] Исследования, начавшиеся в конце 1980-х и начале 1990-х годов, установили, что монады могут объединить, казалось бы, разрозненные проблемы информатики в единую функциональную модель. Теория категорий также предоставляет несколько формальных требований, известных как законы монады , которым должна удовлетворять любая монада и которые можно использовать для проверки монадического кода. [3] [4]

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

Обзор

«Для монады mзначение типа m aпредставляет собой доступ к значению типа aв контексте монады». —К.А. Макканн [6]

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

Монаду можно создать, определив конструктор типа M и две операции:

(Альтернативную, но эквивалентную конструкцию, использующую join функцию вместо bindоператора, можно найти в следующем разделе § Вывод из функторов .)

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

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

Пример: Возможно

Одним из примеров монады является Maybeтип. Неопределенные нулевые результаты — это особая болевая точка, для решения которой многие процедурные языки не предоставляют специальных инструментов, требующих использования шаблона нулевого объекта или проверок для проверки недопустимых значений в каждой операции для обработки неопределенных значений. Это вызывает ошибки и затрудняет создание надежного программного обеспечения, которое корректно обрабатывает ошибки. Тип Maybeзаставляет программиста иметь дело с этими потенциально неопределенными результатами, явно определяя два состояния результата: Just ⌑result⌑, или Nothing. Например, программист может создавать синтаксический анализатор, который должен возвращать промежуточный результат или сигнализировать об условии, которое синтаксический анализатор обнаружил и которое программист также должен обработать. Всего лишь немного дополнительных функциональных особенностей, и этот Maybeтип превращается в полнофункциональную монаду. [б] : 12,3 страницы 148–151. 

В большинстве языков монада Maybe также известна как тип параметра , который представляет собой тип, который отмечает, содержит ли он значение или нет. Обычно они выражаются как некий перечислимый тип . В этом примере Rust мы будем называть его Maybe<T>, и варианты этого типа могут быть либо значением универсального типа T , либо пустым вариантом: Nothing.

// <T> представляет общий тип "T" enum  Maybe < T > { Just ( T ), Nothing , }   

Maybe<T>также можно понимать как тип «обертки», и именно здесь проявляется его связь с монадами. В языках с той или иной формой этого типа Maybeесть функции, которые помогают в их использовании, например составление монадических функций друг с другом и проверка, если a Maybeсодержит значение.

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

fn  разделить ( x : Decimal , y : Decimal ) -> Maybe < Decimal > { if y == 0 { return Nothing } else { return Just ( x / y ) } } // разделить (1.0, 4.0) -> возвращает Just (0.25) // разделить(3.0, 0.0) -> ничего не возвращает                  

Одним из таких способов проверить, Maybeсодержит ли a значение, является использование ifоператоров.

пусть m_x = делить ( 3,14 , 0,0 ); // см. функцию деления выше // Оператор if извлекает x из m_x, если m_x является вариантом Just Maybe if let Just ( x ) = m_x { println! ( "ответ: " , x ) } else { println! ( "деление не удалось, ошибка деления на ноль..." ) }               

Другие языки могут иметь сопоставление с образцом.

пусть результат = разделить ( 3,0 , 2,0 ); результат совпадения { Just ( x ) => println! ( "Ответ: " , x ), Ничего => println! ( «разделение провалилось; мы достанем их в следующий раз». ), }             

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

fn  Chainable_division ( Maybe_x : Maybe < Decimal > , Maybe_y : Maybe < Decimal > ) -> Maybe < Decimal > { match ( Maybe_x , Maybe_y ) { ( Just ( x ), Just ( y )) => { // Если оба входа являются справедливыми, проверьте деление на ноль и разделите соответственно if y == 0 { return Nothing } else { return Just ( x / y ) } }, _ => return Nothing // В противном случае не возвращайте Nothing } } Chainable_division ( chainable_division ( Just ( 2.0 ), Просто ( 0,0 )), Просто ( 1,0 )); // внутри Chainable_division происходит сбой, снаружи Chainable_division не возвращает ничего                                     

Чтобы переписать функции для использования Maybes в этом конкретном примере, требуется много шаблонного кода (посмотрите на все эти Justвыражения!). Вместо этого мы можем использовать так называемый оператор связывания . (также известный как «карта», «плоская карта» или «толчок» [8] : 2205s  ). Эта операция принимает монаду и функцию, которая возвращает монаду, и запускает функцию для внутреннего значения переданной монады, возвращая монаду из функции.

// Пример Rust с использованием ".map". Maybe_x передается через две функции, которые возвращают Maybe<Decimal> и Maybe<String> соответственно. // Как и в случае с обычной композицией функций, входные и выходные данные функций, переходящих друг в друга, должны соответствовать обернутым типам. (т. е. функция add_one должна возвращать Maybe<Decimal>, который затем можно развернуть в Decimal для функции decimal_to_string) let Maybe_x : Maybe < Decimal > = Just ( 1.0 ) let Maybe_result = Maybe_x . карта ( add_one ). карта ( decimal_to_string )      

В Haskell есть оператор связывания или ( >>=), который позволяет использовать эту монадическую композицию в более элегантной форме, похожей на композицию функций . [с] : 150–151 

пополам :: Int -> Maybe Int пополам x | даже x = Just ( x ` div ` 2 ) | нечетный x = ничего . Этот код вдвое уменьшает x пополам. его значение равно нулю, если x не кратно 4, пополам x >> = пополам                       

При >>=наличии chainable_divisionэто можно выразить гораздо более лаконично с помощью анонимных функций (т.е. лямбда-выражений). Обратите внимание в приведенном ниже выражении, как каждая из двух вложенных лямбда-выражений работает с обернутым значением в переданной Maybeмонаде с помощью оператора привязки. [д] : 93 

 Chainable_division ( mx , my ) = mx >>= ( λx -> my >>= ( λy -> Just ( x / y )) )               

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

Монадический тип
Тип А ( Maybe) [б] : 148–151 
Работа агрегата
Преобразователь типа ( Just(x)) [d] : 93 
Операция привязки
Комбинатор для монадических функций ( >>=или .flatMap()) [c] : 150–151 

Это три вещи, необходимые для образования монады. Другие монады могут воплощать разные логические процессы, а некоторые могут иметь дополнительные свойства, но все они будут иметь эти три схожих компонента. [1] [9]

Определение

Более распространенное определение монады в функциональном программировании, использованное в приведенном выше примере, на самом деле основано на тройке Клейсли ⟨T, η, µ⟩, а не на стандартном определении теории категорий. Однако эти две конструкции оказываются математически эквивалентными, поэтому любое определение даст допустимую монаду. Учитывая любые четко определенные базовые типы T , U , монада состоит из трех частей:

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

С алгебраической точки зрения это означает , что любая монада одновременно порождает категорию (называемую категорией Клейсли ) и моноид в категории функторов (от значений до вычислений), с монадической композицией в качестве бинарного оператора в моноиде [8] : 2450  и единица как тождество в монаде.

Применение

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

Обычно программисты используют привязку для объединения монадических функций в последовательность, что привело к тому, что некоторые стали описывать монады как «программируемые точки с запятой», что указывает на то, сколько императивных языков используют точки с запятой для разделения операторов . [1] [5] Однако следует подчеркнуть, что монады на самом деле не упорядочивают вычисления; даже в языках, в которых они используются в качестве центральных функций, более простая композиция функций позволяет упорядочить шаги внутри программы. Общая польза монады скорее заключается в упрощении структуры программы и улучшении разделения задач посредством абстракции. [4] [11]

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

Еще одно примечательное применение монад — это изоляция побочных эффектов, таких как ввод/вывод или изменяемое состояние , в чисто функциональном коде. Даже чисто функциональные языки могут реализовать эти «нечистые» вычисления без монад, в частности, посредством сложной комбинации композиции функций и стиля передачи продолжения (CPS). [2] Однако в случае с монадами большую часть этих каркасов можно абстрагировать, по сути, взяв каждый повторяющийся шаблон в коде CPS и объединив его в отдельную монаду. [4]

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

Приложения

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

Вот лишь несколько приложений, в основе которых лежат монады:

История

Термин «монада» в программировании на самом деле восходит к языкам программирования APL и J , которые имеют тенденцию быть чисто функциональными. Однако в этих языках «монада» — это всего лишь сокращение для функции, принимающей один параметр (функция с двумя параметрами является «диадой» и т. д.). [19]

Математик Роджер Годеман был первым, кто сформулировал концепцию монады (назвав ее «стандартной конструкцией») в конце 1950-х годов, хотя термин «монада», который стал доминировать, был популяризирован теоретиком категорий Сондерсом Мак Лейном . [ нужна цитация ] Форма, определенная выше с использованием связывания , однако, была первоначально описана в 1965 году математиком Генрихом Клейсли , чтобы доказать, что любая монада может быть охарактеризована как соединение между двумя (ковариантными) функторами. [20]

Начиная с 1980-х годов в компьютерном сообществе начало появляться смутное представление о монадном паттерне. По словам исследователя языка программирования Филипа Уодлера , ученый-компьютерщик Джон К. Рейнольдс предвидел несколько аспектов этого языка в 1970-х и начале 1980-х годов, когда он обсуждал ценность стиля передачи продолжения , теории категорий как богатого источника формальной семантики и различие типов между значениями и вычислениями. [4] Исследовательский язык Opal , который активно разрабатывался вплоть до 1990 года, также эффективно основывал ввод-вывод на монадическом типе, но в то время эта связь не была реализована. [21]

Ученый-компьютерщик Эудженио Моджи был первым, кто явно связал монаду теории категорий с функциональным программированием в докладе на конференции в 1989 году [22] , за которым последовала более подробная статья в журнале в 1991 году. В более ранних работах несколько ученых-компьютерщиков продвинулись вперед, используя теория категорий, обеспечивающая семантику лямбда -исчисления . Ключевая идея Моджи заключалась в том, что реальная программа — это не просто функция преобразования значений в другие значения, а скорее преобразование, которое формирует вычисления на основе этих значений. Если это формализовать в терминах теории категорий, это приводит к выводу, что монады представляют собой структуру, представляющую эти вычисления. [3]

Несколько других людей популяризировали и развивали эту идею, в том числе Филип Уодлер и Саймон Пейтон Джонс , оба участвовали в спецификации Haskell. В частности, Haskell использовал проблемную модель «ленивого потока» вплоть до версии 1.2 для согласования ввода-вывода с ленивым вычислением , пока не переключился на более гибкий монадический интерфейс. [23] Сообщество Haskell продолжало применять монады для решения многих задач функционального программирования, и в 2010-х годах исследователи, работающие с Haskell, в конечном итоге признали, что монады являются аппликативными функторами ; [24] [i] и что и монады, и стрелки являются моноидами . [26]

Поначалу программирование с использованием монад в основном ограничивалось Haskell и его производными, но поскольку функциональное программирование оказало влияние на другие парадигмы, многие языки включили монадный шаблон (если не по названию, то по духу). Формулировки теперь существуют в Scheme , Perl , Python , Racket , Clojure , Scala , F# , а также рассматриваются для нового стандарта машинного обучения . [ нужна цитата ]

Анализ

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

Проверка законов монады

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

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

Закон 1: эта(а) >>= f(x) ⇔ (Просто а) >>= f(x) ⇔ f(a)
Закон 2: ma >>= eta(x) ⇔ ma если ма (просто а) , то эта(а) ⇔ Просто иначе  или Ничего ⇔ Ничего конец, если
Закон 3:  ( ma >>= f(x) ) >>= g(y) ⇔ ma >>= ( f(x) >>= g(y) ) если (ma >>= f(x)) равно (Только b) , то  если ma равно (Только а), то g(ma >>= f(x)) (f(x) >>= g(y)) a еще  еще Ничего ничего конец, если  конец, еслиесли ma равно (Просто а) и f(a) равно (Просто b), то  (г ∘ е) а иначе, если ma равно (просто a), а f(a) равно ничем, то Ничего еще Ничего конец, если

Вывод из функторов

Хотя в информатике это случается реже, но можно напрямую использовать теорию категорий, которая определяет монаду как функтор с двумя дополнительными естественными преобразованиями . [j] Итак, для начала структура требует функции высшего порядка (или «функционала») с именем map , чтобы квалифицироваться как функтор:

map φ : (a → b) → (ma → mb)

Однако это не всегда является серьезной проблемой, особенно когда монада получена из уже существующего функтора, после чего монада автоматически наследует карту . (По историческим причинам это mapназывается fmapв Haskell.)

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

(unit ∘ φ) x ↔ ((map φ) ∘ unit) x ↔ x[27]

Последний переход от аппликативного функтора к монаде происходит со вторым преобразованием, функцией соединения (в теории категорий это естественное преобразование, обычно называемое μ ), которое «сглаживает» вложенные приложения монады:

join(mma) : M (M T) → M T

В качестве характеристической функции соединение также должно удовлетворять трем вариантам законов монады:

(join ∘ (map join)) mmma ↔ (join ∘ join) mmma ↔ ma
(join ∘ (map unit)) ma ↔ (join ∘ unit) ma ↔ ma
(join ∘ (map map φ)) mma ↔ ((map φ) ∘ join) mma ↔ mb

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

(map φ) ma ↔ ma >>= (unit ∘ φ)
join(mma) ↔ mma >>= id
ma >>= f ↔ (join ∘ (map f)) ma[28]

Другой пример: Список

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

Встраивание простого значения в список также тривиально в большинстве языков:

единица измерения (х) = [х]

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

Однако процедура применения любой простой функции ко всему списку, другими словами , карте , проста:

(отображение φ) xlist = [ φ(x1), φ(x2), ..., φ(xn) ]

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

join(xlistlist) = join([xlist1, xlist2, ..., xlistn]) = xlist1 ++ xlist2 ++ ... ++ xlistn

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

Монада Listможет значительно упростить использование многозначных функций, таких как комплексные корни. [29]
(xlist >>= f) = присоединиться ∘ (карта f) xlist

Одно из применений этого монадического списка — представление недетерминированных вычислений . Listможет хранить результаты для всех путей выполнения в алгоритме, а затем сгущаться на каждом этапе, чтобы «забыть», какие пути к каким результатам привели (иногда важное отличие от детерминированных исчерпывающих алгоритмов). [ нужна цитата ] Еще одним преимуществом является то, что проверки могут быть встроены в монаду; определенные пути можно прозрачно обрезать в первой точке сбоя, без необходимости переписывать функции в конвейере. [28]

Вторая ситуация, в которой Listесть смысл, — это составление многозначных функций . Например, комплексный корень n - го числа должен давать n различных комплексных чисел, но если затем из этих результатов берется еще один корень m -й степени, окончательные значения m•n должны быть идентичны выходным данным корня m•n- й степени. . полностью автоматизирует эту проблему, сжимая результаты каждого шага в плоский, математически правильный список. [29]List

Техники

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

Синтаксический сахар.mw-parser-output .vanchor>:target~.vanchor-text{background-color:#b1d2ff}do-нотация

Хотя открытое использование связывания часто имеет смысл, многие программисты предпочитают синтаксис, который имитирует императивные операторы (так называемые do-notation в Haskell, Performance-notation в OCaml , вычислительные выражения в F# , [30] и для понимания в Scala ). Это всего лишь синтаксический сахар , который маскирует монадический конвейер под блок кода ; затем компилятор незаметно преобразует эти выражения в базовый функциональный код.

Перевод addфункции из Maybeязыка Haskell может продемонстрировать эту возможность в действии. Немонадическая версия addHaskell выглядит так:

add mx my = case mx of Nothing -> Nothing Just x -> case my of Nothing -> Nothing Just y -> Just ( x + y )                         

В монадическом Haskell returnэто стандартное имя для unit , плюс лямбда-выражения должны обрабатываться явно, но даже с учетом этих технических особенностей монада Maybeобеспечивает более чистое определение:

добавить mx my = mx >>= ( \ x -> my >>= ( \ y -> return ( x + y )))               

Однако с помощью do-нотации это можно еще больше свести к очень интуитивно понятной последовательности:

добавить mx my = do x <- mx y <- my return ( x + y )              

Второй пример показывает, как Maybeможно использовать совершенно другой язык: F#. С помощью вычислительных выражений функцию «безопасного деления», которая возвращает Noneнеопределенный операнд или деление на ноль, можно записать так:

let  readNum  ()  =  let  s  =  Console . ReadLine ()  let  succ , v  =  Int32 . TryParse ( s )  if  ( succ )  then  Some ( v )  else  Noneпусть  secure_div  =  может быть  {  пусть !  x  =  readNum ()  пусть !  y  =  readNum ()  if  ( y  =  0 )  then  None  else  return  ( x  /  y )  }

Во время сборки компилятор внутренне «десахарит» эту функцию, превращая ее в более плотную цепочку вызовов связывания :

может быть . Delay ( fun  ()  ->  возможно . Bind ( readNum () ,  fun  x  ->  возможно . Bind ( readNum () ,  fun  y  ->  if  ( y = 0 )  then  None  else  возможно . Return ( x  /  y ))) )

Последний пример: даже сами общие законы монады могут быть выражены в до-нотации:

сделать { х <- вернуть v ; ж x } == do { f v } do { x <- m ; return x } == do { m } do { y <- do { x <- m ; х } ; г y } == do { x <- m ; y <- е х ; г й }                                                     

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

Общий интерфейс

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

Операторы

Монадический код часто можно еще больше упростить за счет разумного использования операторов. Функционал карты может быть особенно полезен , поскольку он работает не только со специальными монадическими функциями; до тех пор, пока монадическая функция должна работать аналогично предопределенному оператору, карту можно использовать для мгновенного « преобразования » более простого оператора в монадический. [k] С помощью этого метода определение addиз Maybeпримера можно свести к:

add(mx,my) = карта (+)

Этот процесс можно было бы продвинуть еще на один шаг вперед, определив addне только for Maybe, но и весь Monadинтерфейс. При этом любая новая монада, соответствующая интерфейсу структуры и реализующая собственную карту , сразу же унаследует расширенную версию add. Единственное необходимое изменение в функции — это обобщение сигнатуры типа:

добавить: (Номер монады, Номер монады) → Номер монады [32]

Еще один монадический оператор, который также полезен для анализа, — это монадическая композиция ( >=>здесь представленная в виде инфикса), которая позволяет объединять монадические функции в более математический стиль:

(f >=> g)(x) = f(x) >>= g

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

(единица >=> г) ↔ г(f >=> единица) ↔ f(f >=> g) >=> h ↔ f >=> (g >=> h) [1]

В свою очередь, приведенное выше показывает значение блока «do» в Haskell:

делать _p <- f(x) _q <- г(_p) h(_q) ↔ ( f >=> g >=> h )(x)

Больше примеров

Идентификационная монада

Самая простая монада — это монада Identity , которая просто аннотирует простые значения и функции, чтобы удовлетворить законам монады:

идентификатор нового типа T = Tединица(х) = х(х >>= е) = е(х)

Identityна самом деле имеет допустимое применение, например, предоставление базового варианта для рекурсивных преобразователей монад . Его также можно использовать для выполнения базового назначения переменных внутри блока императивного стиля. [л] [ нужна ссылка ]

Коллекции

Любая коллекция с правильным добавлением уже является свободным моноидом, но оказывается, что Listэто не единственная коллекция , которая также имеет четко определенное соединение и квалифицируется как монада. Можно даже мутировать Listв эти другие монадические коллекции, просто наложив специальные свойства на добавление : [m] [n]

IO-монада (Haskell)

Как уже упоминалось, чистый код не должен иметь неуправляемых побочных эффектов, но это не мешает программе явно описывать эффекты и управлять ими. Эта идея является центральной для монады IO Haskell , где объект типа IO aможно рассматривать как описание действия, которое должно быть выполнено в мире, при необходимости предоставляя информацию о мире типа a. Действие, которое не предоставляет никакой информации о мире, имеет тип IO (), «предоставляющий» фиктивное значение (). Когда программист привязывает IOзначение к функции, функция вычисляет следующее действие, которое необходимо выполнить, на основе информации о мире, предоставленной предыдущим действием (ввод от пользователей, файлов и т. д.). [23] Самое главное, поскольку значение монады IO может быть привязано только к функции, которая вычисляет другую монаду IO, функция привязки накладывает дисциплину на последовательность действий, при которой результат действия может быть предоставлен только функции. который вычислит следующее действие, которое необходимо выполнить. Это означает, что действия, которые не нужно выполнять, никогда не выполняются, а действия, которые необходимо выполнить, имеют четко определенную последовательность, что решает проблему (IO) действий, которые не являются ссылочно прозрачными .

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

doFileExist :: FilePath -> IO Bool RemoveFile :: FilePath -> IO ()          

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

IOоднако не ограничивается только файловым вводом-выводом; он даже допускает пользовательский ввод-вывод и, наряду с императивным синтаксическим сахаром, может имитировать типичное «Hello, World!» программа :

main :: IO () main = do putStrLn "Привет, мир!" putStrLn "Как тебя зовут, пользователь?" name <- getLine putStrLn ( "Приятно познакомиться, " ++ name ++ "!" )                  

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

main :: IO () main = putStrLn "Привет, мир!" >> putStrLn "Как тебя зовут, пользователь?" >> getLine >>= ( \ name -> putStrLn ( "Приятно познакомиться, " ++ name ++ "!" )                     

Писательская монада (JavaScript)

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

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

const Writer = значение => [ значение , []];      

Определить единицу измерения также очень просто:

константная единица = значение => [ значение , []];      

Требуется только модульWriter для определения простых функций, выводящих объекты с отладочными примечаниями:

const Squared = x => [ x * x , [ ` ${ x } был возведен в квадрат.` ]]; const halfed = x => [ x / 2 , [ ` ${ x } было уменьшено вдвое.` ]];                

Настоящая монада по-прежнему требует привязки , но для Writerэто означает простое объединение вывода функции в связанный список монады:

const связывание = ( писатель , преобразование ) => { const [ значение , журнал ] = писатель ; const [ результат , обновления ] = преобразование ( значение ); возврат [ результат , журнал . concat ( обновления )]; };                   

Примеры функций теперь могут быть объединены в цепочку с помощью связывания , но определение версии монадической композиции (называемой pipelogздесь) позволяет применять эти функции еще более лаконично:

const Pipelog = ( писатель , ... преобразует ) => преобразует . уменьшить ( связать , писатель );       

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

трубопровод ( единица ( 4 ), квадрат , пополам ); // Результирующий объект записи = [8, ['4 было возведено в квадрат.', '16 было уменьшено пополам.']]  

Монада среды

Монада среды (также называемая монадой чтения и монадой функции ) позволяет вычислениям зависеть от значений из общей среды. Конструктор типа монады сопоставляет тип T функциям типа ET , где E — тип общей среды. Функции монады:

Полезны следующие монадические операции:

Операция Ask используется для получения текущего контекста, а операция local выполняет вычисления в измененном подконтексте. Как и в монаде состояния, вычисления в монаде среды могут быть вызваны простым предоставлением значения среды и применением его к экземпляру монады.

Формально значение в монаде окружения эквивалентно функции с дополнительным анонимным аргументом; return иbind эквивалентны комбинаторам K и S соответственно в исчислении комбинаторов SKI .

Государственные монады

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

Тип Состояние s t = s -> ( t , s )        

Обратите внимание, что эта монада принимает параметр типа — тип информации о состоянии. Операции монады определяются следующим образом:

-- «return» возвращает заданное значение без изменения состояния. return x = \ s -> ( x , s ) — «bind» изменяет m так, что оно применяет f к своему результату. m >>= f = \ r -> пусть ( x , s ) = m r in ( f x ) s                     

Полезные операции состояния включают в себя:

get = \ s -> ( s , s ) — проверим состояние на этом этапе вычислений. put s = \ _ -> ( () , s ) — Заменяем состояние. изменить f = \ s -> ( () , f s ) — обновить состояние                     

Другая операция применяет монаду состояния к заданному начальному состоянию:

runState :: State s a -> s -> ( a , s ) runState t s = t s              

do-блоки в монаде состояния — это последовательности операций, которые могут проверять и обновлять данные состояния.

Неформально, монада состояния типа состояния S отображает тип возвращаемых значений T в функции типа , где S — базовое состояние. Функция возврата и привязки :

.

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

Монада продолжения

Монада продолжения [o] с возвращаемым типом R отображает тип T в функции типа . Он используется для моделирования стиля передачи продолжения . Функции возврата и привязки следующие:

Функция вызова с текущим продолжением определяется следующим образом:

Протоколирование программы

Следующий код является псевдокодом.Предположим, у нас есть две функции fooи barс типами

foo : int -> int bar : int -> int        

То есть обе функции принимают целое число и возвращают другое целое число. Затем мы можем применить функции последовательно следующим образом:

фу ( бар х )  

Где результат является результатом fooприменения к результату barприменения к x.

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

foo : int -> int * строка bar : int -> int * строка            

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

К сожалению, это означает, что мы больше не можем составлять foo и bar, поскольку их тип ввода intнесовместим с типом вывода int * string. И хотя мы можем снова добиться компонуемости, изменив типы каждой функции на int * string -> int * string, это потребует от нас добавления шаблонного кода к каждой функции для извлечения целого числа из кортежа, что будет утомительно по мере увеличения количества таких функций.

Вместо этого давайте определим вспомогательную функцию, которая абстрагирует этот шаблон:

связывание : int * строка -> ( int -> int * строка ) -> int * строка              

bindпринимает кортеж целых чисел и строк, затем принимает функцию (например foo, ), которая отображает целое число в кортеж целых чисел и строк. Его выходные данные представляют собой кортеж целых чисел и строк, который является результатом применения входной функции к целому числу внутри входного целого числа и кортежа строк. Таким образом, нам нужно написать шаблонный код только один раз, чтобы извлечь целое число из кортежа в формате bind.

Теперь мы восстановили некоторую компоновку. Например:

привязать ( привязать ( x , s ) бар ) foo    

Где (x,s)целое число и строковый кортеж. [п]

Чтобы сделать преимущества еще более очевидными, давайте определим инфиксный оператор как псевдоним для bind:

( >>= ) : int * строка -> ( int -> int * строка ) -> int * строка              

Так что это t >>= fто же самое, что и bind t f.

Тогда приведенный выше пример станет:

(( x , s ) >>= bar ) >>= foo    

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

возврат : int -> int * строка      

Который обертывается xв кортеж, описанный выше.

Теперь у нас есть хороший конвейер для регистрации сообщений:

(( return x ) >>= bar ) >>= foo     

Это позволяет нам легче регистрировать влияние barи fooна x.

int * stringобозначает псевдокодированное монадическое значение . [p] bind и returnаналогичны соответствующим одноименным функциям. Фактически, int * string, bind, и returnобразуют монаду.

Вариации

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

Аддитивные монады

Аддитивная монада — это монада, наделенная дополнительным замкнутым ассоциативным бинарным оператором mplus и единичным элементом под mplus , называемым mzero . Монаду Maybeможно считать аддитивной, с Nothingmzero и вариацией оператора OR как mplus . Listтакже является аддитивной монадой, в которой пустой список []действует как mzero , а оператор конкатенации ++— как mplus .

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

В терминах теории категорий аддитивная монада квалифицируется один раз как моноид по монадическим функциям с привязкой (как и все монады) и снова по монадическим значениям через mplus . [33] [д]

Свободные монады

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

Например, если полностью работать с маркерами Justи Nothing, Maybeмонада фактически является свободной монадой. ListС другой стороны, монада не является свободной монадой, поскольку она вводит в свое определение дополнительные, конкретные факты о списках (например, Append ) . Последний пример — абстрактная свободная монада:

данные Свободно f a = Чисто a | Свободно ( ж ( Свободно фа ) )            единица :: a -> Свободная единица f a x = Чистый x          привязка :: Функтор f => Свободно f a -> ( a -> Свободно f b ) -> Свободно f b привязка ( Pure x ) f = f x привязка ( Свободно x ) f = Свободно ( fmap ( \ y -> привязка у е ) х )                                   

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

Намеренное использование свободных монад на первый взгляд может показаться непрактичным, но их формальная природа особенно хорошо подходит для решения синтаксических задач. Свободную монаду можно использовать для отслеживания синтаксиса и типа, оставляя семантику на потом, и в результате она нашла применение в синтаксических анализаторах и интерпретаторах . [34] Другие применяли их и к более динамичным, оперативным задачам, таким как предоставление итераций внутри языка. [35]

Комонады

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

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

единица(ва) : WT → T
(wa =>> f) : (WU, WU → T) → WT [r]

«Продление» и «соединение» также должны удовлетворять двойственным законам монады:

единица ∘ ( (wa =>> f) → wb ) ↔ f(wa) → bва =>> единица ↔ ваwa ( (=>> f(wx = wa)) → wb (=>> g(wy = wb)) → wc )( wa (=>> f(wx = wa)) → wb ) (=>> g(wy = wb)) → wc

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

дубликат(ва): WT → W (WT)

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

((дубликат карты) ∘ дубликат) wa ↔ (дубликат ∘ дубликат) wa ↔ wwwa((единица карты) ∘ дубликат) wa ↔ (единица ∘ дубликат) wa ↔ wa((карта карта φ) ∘ дубликат) wa ↔ (дубликат ∘ (карта φ)) wa ↔ wwb

Как и в случае с монадами, две формы могут быть преобразованы автоматически:

(карта φ) wa ↔ wa =>> (φ ∘ единица) wxдубликат wa ↔ wa =>> wx
wa =>> f(wx) ↔ ((map f) ∘ дубликат) wa

Простым примером является comonad Product , который выводит значения на основе входного значения и общих данных среды. Фактически, Productкомонада — это просто двойник монады Writerи фактически то же самое, что и Readerмонада (обе обсуждаются ниже). Productи Readerразличаются только тем, какие сигнатуры функций они принимают, и как они дополняют эти функции путем переноса или развертывания значений.

Менее тривиальный пример — комонада Stream , которую можно использовать для представления потоков данных и присоединения фильтров к входящим сигналам с помощью расширения . Фактически, хотя они и не так популярны, как монады, исследователи обнаружили, что комонады особенно полезны для потоковой обработки и моделирования программирования потоков данных . [36] [37]

Однако из-за их строгих определений нельзя просто перемещать объекты взад и вперед между монадами и комонадами. Будучи еще более высокой абстракцией, стрелки могут включать в себя обе структуры, но поиск более детальных способов объединения монадического и комонадического кода является активной областью исследований. [38] [39]

Смотрите также

Альтернативы модельным расчетам:

Связанные концепции дизайна:

Обобщения монад:

Примечания

  1. ^ В связи с тем, что функции с несколькими свободными переменными широко распространены в программировании, монады, описанные в этой статье, технически являются тем, что теоретики категорий назвали бы сильными монадами . [3]
  2. ^ ab Конкретную мотивацию для Maybe можно найти в (Hutton 2016). [7]
  3. ^ ab Хаттон абстрагирует a bind, который, если ему задан тип a , который может потерпеть неудачу, и отображение ab , которое может потерпеть неудачу, дает результат b , который может потерпеть неудачу. (Хаттон, 2016) [7]
  4. ^ ab (Хаттон 2016) отмечает, что «Просто» может обозначать успех, а «Ничто» может обозначать неудачу. [7]
  5. ^ Семантически M нетривиален и представляет собой эндофунктор над категорией всех правильно типизированных значений:
  6. ^ Хотя в терминах программирования это (параметрически полиморфная) функция, единица (часто называемая η в теории категорий) математически является естественным преобразованием , которое отображает между функторами :
  7. ^ bind , с другой стороны, не является естественным преобразованием в теории категорий, а скорее расширением , которое превращает отображение (от значений к вычислениям) в морфизм между вычислениями:
  8. ^ Строго говоря, связывание не может быть формально ассоциативным во всех контекстах, поскольку оно соответствует применению в рамках лямбда-исчисления , а не математики. В строгом лямбда-исчислении для оценки привязки может потребоваться сначала обернуть правильный термин (при привязке двух монадических значений) или саму привязку (между двумя монадическими функциями) в анонимную функцию , чтобы по-прежнему принимать входные данные слева. [10]
  9. ^ Начиная с версии GHC 7.10.1 и в дальнейшем Haskell начал применять предложение Haskell 2014 Applicative Monad (AMP), которое требует вставки 7 строк кода в любые существующие модули, использующие монады. [25]
  10. ^ Эти естественные преобразования обычно обозначаются как морфизмы η, µ. То есть: η, µ обозначают единицу и соединение соответственно.
  11. ^ Некоторые языки, такие как Haskell, даже предоставляют псевдоним для карты в других контекстах, называемый lift, а также несколько версий для разного количества параметров, здесь эта деталь игнорируется.
  12. ^ В теории категорий монаду Identityтакже можно рассматривать как возникающую в результате соединения любого функтора с его обратным.
  13. ^ Теория категорий рассматривает эти коллекции монад как соединения между свободным функтором и различными функторами из категории множеств в категорию моноидов .
  14. ^ Здесь задача программиста — сконструировать подходящий моноид или, возможно, выбрать моноид из библиотеки.
  15. ^ Читатель, возможно, пожелает проследить за темой Макканна [6] и сравнить ее с типами ниже.
  16. ^ ab В этом случае элемент вставленbind в место , где раньше был только элемент; то есть программист построил дополнение : кортеж , обозначенный в § псевдокода выше.stringinteger(x,s)int * string
  17. ^ Алгебраически отношения между двумя (некоммутативными) аспектами моноида напоминают отношения почти полукольца , и некоторые аддитивные монады действительно квалифицируются как таковые. Однако не все аддитивные монады удовлетворяют законам распределения даже почти полукольца. [33]
  18. ^ В Haskell расширение на самом деле определяется с заменой входных данных, но, поскольку в этой статье не используется каррирование, оно определяется здесь как точный аналог связывания .

Рекомендации

  1. ^ abcdefg О'Салливан, Брайан; Герцен, Джон; Стюарт, Дон (2009). «Монады». Реальный мир Haskell. Севастополь, Калифорния: O'Reilly Media. глава 14. ISBN 978-0596514983.
  2. ^ Аб Вадлер, Филип (июнь 1990 г.). Понимание монад . Конференция ACM по LISP и функциональному программированию. Ницца, Франция. CiteSeerX 10.1.1.33.5381 . 
  3. ^ abc Могги, Эухенио (1991). «Представления о вычислениях и монадах» (PDF) . Информация и вычисления . 93 (1): 55–92. CiteSeerX 10.1.1.158.5275 . дои : 10.1016/0890-5401(91)90052-4. 
  4. ^ abcde Wadler, Филип (январь 1992 г.). Сущность функционального программирования . 19-й ежегодный симпозиум ACM по принципам языков программирования. Альбукерке, Нью-Мексико. CiteSeerX 10.1.1.38.9516 . 
  5. ^ Аб Худак, Пол ; Петерсон, Джон; Фазель, Джозеф (1999). «О монадах». Небольшое введение в Haskell 98. Глава 9.
  6. ^ ab CA Ответ Макканна (23 июль 2010, в 23:39) Как и почему работает монада Haskell Cont?
  7. ^ abc Грэм Хаттон (2016) Программирование на Haskell, 2-е издание
  8. ^ Аб Бекерман, Брайан (21 ноября 2012 г.). «Не бойтесь Монады». YouTube .
  9. ^ Спайви, Майк (1990). «Функциональная теория исключений» (PDF) . Наука компьютерного программирования . 14 (1): 25–42. дои : 10.1016/0167-6423(90)90056-J .
  10. ^ «Законы монады». ХаскеллВики . Haskell.org . Проверено 14 октября 2018 г.
  11. ^ «Чем не является Монада» . 7 октября 2018 г.
  12. ^ Де Мойтер, Вольфганг (1997). Монады как теоретическая основа АОП (PDF) . Международный семинар по аспектно-ориентированному программированию в ЭКООП. Ювяскюля, Финляндия. CiteSeerX 10.1.1.25.8262 . 
  13. ^ «Монада (без метафор)» . ХаскеллВики . 1 ноября 2009 года . Проверено 24 октября 2018 г.
  14. ^ О'Салливан, Брайан; Герцен, Джон; Стюарт, Дон (2009). «Использование парсека». Реальный мир Haskell. Севастополь, Калифорния: O'Reilly Media. глава 16. ISBN 978-0596514983.
  15. Стюарт, Дон (17 мая 2007 г.). «Сверните свой оконный менеджер: отслеживание фокуса с помощью молнии». Control.Monad.Writer . Архивировано из оригинала 20 февраля 2018 года . Проверено 19 ноября 2018 г.
  16. ^ Бентон, Ник (2015). «Категорические монады и компьютерное программирование» (PDF) . Лондонское математическое общество Impact150 Stories . 1 . Проверено 19 ноября 2018 г.
  17. ^ Киселев, Олаг (2007). «Продолжения с разделителями в операционных системах». Моделирование и использование контекста . Конспекты лекций по информатике. Том. 4635. Шпрингер Берлин Гейдельберг. страницы 291-302. дои : 10.1007/978-3-540-74255-5_22. ISBN 978-3-540-74255-5.
  18. Мейер, Эрик (27 марта 2012 г.). «Ваша мышь — это база данных». Очередь АКМ . 10 (3): 20–33. дои : 10.1145/2168796.2169076 .
  19. ^ Айверсон, Кеннет (сентябрь 1987 г.). «Словарь АПЛ». APL Котировка Quad . 18 (1): 5–40. дои : 10.1145/36983.36984. ISSN  1088-6826. S2CID  18301178 . Проверено 19 ноября 2018 г.
  20. ^ Клейсли, Генрих (1965). «Каждая стандартная конструкция индуцируется парой сопряженных функторов» (PDF) . Труды Американского математического общества . 16 (3): 544–546. дои : 10.1090/S0002-9939-1965-0177024-4 . Проверено 19 ноября 2018 г.
  21. ^ Питер Пеппер, изд. (ноябрь 1997 г.). Язык программирования Opal (Технический отчет) (5-е исправленное изд.). Fachbereich Informatik, Технический университет Берлина. CiteSeerX 10.1.1.40.2748 . 
  22. ^ Моджи, Эухенио (июнь 1989 г.). Вычислительное лямбда-исчисление и монады (PDF) . Четвертый ежегодный симпозиум по логике в информатике. Пасифик Гроув, Калифорния. CiteSeerX 10.1.1.26.2787 . 
  23. ^ аб Пейтон Джонс, Саймон Л .; Уодлер, Филип (январь 1993 г.). Императивное функциональное программирование (PDF) . 20-й ежегодный симпозиум ACM по принципам языков программирования. Чарльстон, Южная Каролина. CiteSeerX 10.1.1.53.2504 . 
  24. ^ Брент Йорги Типклассопедия
  25. ^ Переполнение стека (8 сентября 2017 г.) Определение новой монады в Haskell не вызывает экземпляра Applicative.
  26. ^ Брент Йорги Моноиды
  27. ^ «Аппликативный функтор». ХаскеллВики . Хаскелл.орг. 7 мая 2018 г. Архивировано из оригинала 30 октября 2018 г. Проверено 20 ноября 2018 г.
  28. ^ Аб Гиббард, Кейл (30 декабря 2011 г.). «Монады как контейнеры». ХаскеллВики . Хаскелл.орг. Архивировано из оригинала 14 декабря 2017 года . Проверено 20 ноября 2018 г.
  29. ^ Аб Пипони, Дэн (7 августа 2006 г.). «Вы могли бы изобрести монады! (И, возможно, вы уже это сделали)». Район бесконечности . Архивировано из оригинала 24 октября 2018 года . Проверено 16 октября 2018 г.
  30. ^ «Некоторые подробности о вычислительных выражениях F #» . 21 сентября 2007 года . Проверено 9 октября 2018 г.
  31. ^ «Не делать обозначений, которые считаются вредными» . ХаскеллВики . Проверено 12 октября 2018 г.
  32. Джайлз, Бретт (12 августа 2013 г.). «Подъем». ХаскеллВики . Хаскелл.орг. Архивировано из оригинала 29 января 2018 года . Проверено 25 ноября 2018 г.
  33. ^ аб Ривас, Эксекиэль; Яскелев, Мауро; Шрийверс, Том (июль 2015 г.). От моноидов к почти полукольцам: суть MonadPlus и Альтернатива (PDF) . 17-й Международный симпозиум ACM по принципам и практике декларативного программирования. Сиена, Италия. CiteSeerX 10.1.1.703.342 . 
  34. ^ Свирстра, Воутер (2008). «Типы данных по выбору» (PDF) . Функциональная жемчужина. Журнал функционального программирования . Издательство Кембриджского университета. 18 (4): 423–436. CiteSeerX 10.1.1.101.4131 . дои : 10.1017/s0956796808006758. ISSN  1469-7653. S2CID  21038598. 
  35. Киселёв, Олег (май 2012 г.). Шрийверс, Том; Тиманн, Питер (ред.). Итераторы (PDF) . Международный симпозиум по функциональному и логическому программированию. Конспекты лекций по информатике. Том. 7294. Кобе, Япония: Springer-Verlag. стр. 166–181. дои : 10.1007/978-3-642-29822-6_15. ISBN 978-3-642-29822-6.
  36. ^ Уусталу, Тармо; Вене, Вармо (июль 2005 г.). Хорват, Золтан (ред.). Сущность программирования потоков данных (PDF) . Первая летняя школа Центральноевропейского функционального программирования. Конспекты лекций по информатике. Том. 4164. Будапешт, Венгрия: Springer-Verlag. стр. 135–167. CiteSeerX 10.1.1.62.2047 . ISBN  978-3-540-46845-5.
  37. ^ Уусталу, Тармо; Вене, Вармо (июнь 2008 г.). «Комонадические понятия вычислений». Электронные заметки по теоретической информатике . Эльзевир. 203 (5): 263–284. дои : 10.1016/j.entcs.2008.05.029. ISSN  1571-0661.
  38. ^ Пауэр, Джон; Ватанабэ, Хироши (май 2002 г.). «Объединение монады и комонады» (PDF) . Теоретическая информатика . Эльзевир. 280 (1–2): 137–162. CiteSeerX 10.1.1.35.4130 . дои : 10.1016/s0304-3975(01)00024-x. ISSN  0304-3975. 
  39. ^ Габорди, Марко; Кацумата, Син-я; Орчард, Доминик; Бревар, Флавиен; Уусталу, Тармо (сентябрь 2016 г.). Объединение эффектов и коэффектов посредством оценки (PDF) . 21-я Международная конференция ACM по функциональному программированию. Нара, Япония: Ассоциация вычислительной техники. стр. 476–489. дои : 10.1145/2951913.2951939. ISBN 978-1-4503-4219-3.

Внешние ссылки

Ссылки на HaskellWiki:

Учебники:

Интересные случаи: