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

Из Википедии, бесплатной энциклопедии

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

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

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

Обзор [ править ]

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

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

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

  • return :: a -> M a (часто также называемый unit ), который получает значение типа a и оборачивает его в монадическое значение типа M a, и
  • bind :: (M a) -> (a -> M b) -> (M b) (обычно представляется как >>=), который получает монадическое значение M a и функция f который принимает значения базового типа a. Привязать развертки a, применяется f к нему и может обрабатывать результат f как монадическое значение M b.

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

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

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

Пример: Возможно [ править ]

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

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

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

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

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

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 содержит значение, следует использовать 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] : 2205p ). Эта операция принимает монаду и функцию, которая возвращает монаду, и запускает функцию для внутреннего значения переданной монады, возвращая монаду из функции.

// Пример 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)) [д] : 93 
Операция привязки
Комбинатор для монадических функций ( >>= или .flatMap()) [с] : 150–151 

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

Определение [ править ]

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

  • Конструктор типа M , создающий монадический тип MT. [Это]
  • Преобразователь типов , часто называемый unit или return , который встраивает объект x в монаду:
    unit : T → M T[ф]
  • Комбинатор . , обычно называемый привязкой (как при привязке переменной ) и представленный инфиксным оператором >>= или метод с именем FlatMap , который разворачивает монадическую переменную, а затем вставляет ее в монадическую функцию/выражение, в результате чего получается новое монадическое значение:
    (>>=) : (M T, T → M U) → M U[г] так что если mx : M T и f : T → M U, затем (mx >>= f) : M 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] [я] и что и монады, и стрелки являются моноидами . [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))  если  (Просто б)  , тогда                 если  ма  то  (Просто а)  , 
              g(ma >>= f(x)) (f(x) >>= g(y)) a 
          еще                                              еще 
              Ничего ничего 
          конец, если                                            конец, если  если  ma  равно  (Просто а)  и  f(a)  равно  (Просто b)  , то       
                         (г ∘ е) а 
                     иначе, если  ma  равно  (просто a)  , а  f(a)  равно ничем, то 
                         Ничего 
                     еще 
                         Ничего 
                     конец, если 

Вывод из функторов [ править ]

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

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

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

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

(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 значения через конвейер монадических функций:

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

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

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

Техники [ править ]

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

Синтаксический сахар do-нотация [ править ]

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

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

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   Нет 

 let   secure_div   =  
   возможно   {  
     let  !    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интерфейс с прототипами функций , отношениями подтипов и другими общими фактами. Помимо обеспечения форы для разработки и гарантии того, что новая монада наследует функции супертипа (например, функторы), проверка дизайна монады на соответствие интерфейсу добавляет еще один уровень контроля качества. [ нужна цитата ]

Операторы [ править ]

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

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

Этот процесс можно было бы продвинуть еще на один шаг вперед, определив add не только для 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 в эти другие монадические коллекции, просто наложив специальные свойства на добавление : [м] [н]

Коллекция Свойства моноида
Список Бесплатно
Конечное мультимножество коммутативный
Конечный набор Коммутативный и идемпотентный
Конечные перестановки Некоммутативный [ нужны разъяснения ] и идемпотент

IO-монада (Haskell) [ править ]

Как уже упоминалось, чистый код не должен иметь неуправляемых побочных эффектов, но это не мешает программе явно описывать эффекты и управлять ими. Хаскеля Эта идея является центральной в монаде IO , где объект типа IO a можно рассматривать как описание действия, которое должно быть выполнено в мире, возможно, предоставляющее информацию о мире типа a. Действие, не предоставляющее никакой информации о мире, имеет тип IO (), "предоставляя" фиктивное значение (). Когда программист связывает IO значение функции, функция вычисляет следующее действие, которое должно быть выполнено, на основе информации о мире, предоставленной предыдущим действием (ввод от пользователей, файлов и т. д.). [23] Что наиболее важно, поскольку значение монады ввода-вывода может быть связано только с функцией, которая вычисляет другую монаду ввода-вывода, функция связывания налагает дисциплину последовательности действий, при которой результат действия может быть предоставлен только функции, которая будет вычислять следующее действие, которое необходимо выполнить. Это означает, что действия, которые не нужно выполнять, никогда не выполняются, а действия, которые необходимо выполнить, имеют четко определенную последовательность, что решает проблему (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 функциям типа E T , где E — тип общей среды. Функции монады:

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

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

Формально значение в монаде окружения эквивалентно функции с дополнительным анонимным аргументом; return иbind S эквивалентны комбинаторам K и 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 — основное состояние. возврата и привязки : Функция

.

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

Продолжение монады [ править ]

продолжения Монада [О] с типом возврата 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 обозначает псевдокодированное монадическое значение . [п] bind и returnаналогичны соответствующим одноименным функциям. Фактически, int * string, bind, и return образуют монаду.

Вариации [ править ]

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

Аддитивные монады [ править ]

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

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

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

Свободные монады [ править ]

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

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

данные   Свободно   f   a 
   =   Чисто   a 
   |    Свободный   (  f   (  Free   f   a  )) 

 unit   ::   a   ->   Свободный   f   a 
 unit   x   =   Чистый   x 

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

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

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

Комонады [ править ]

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

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

  • Конструктор типа W , который отмечает тип высшего порядка WT.
  • Двойник unit , называемый здесь counit , извлекает базовое значение из комонады:
единица(ва) : WT → T
 
  • Отмена привязки (также обозначаемая как =>>), расширяющие цепочку приводящих функций:
(wa =>> f) : (WU, WU → T) → WT [р] 

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

единица ∘  (  (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]

См. также [ править ]

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

  • Системы эффектов (особенно алгебраические обработчики эффектов) — это другой способ описания побочных эффектов как типов.
  • Типы уникальности — это третий подход к обработке побочных эффектов в функциональных языках.

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

  • В аспектно-ориентированном программировании особое внимание уделяется выделению вспомогательного бухгалтерского кода для повышения модульности и простоты.
  • Инверсия управления — это абстрактный принцип вызова определенных функций из всеобъемлющей структуры.
  • Классы типов — это особая функция языка, используемая для реализации монад и других структур в Haskell.
  • Шаблон декоратора это более конкретный, специальный способ достижения аналогичных преимуществ в объектно-ориентированном программировании.

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

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

Примечания [ править ]

  1. ^ В связи с тем, что функции с несколькими свободными переменными широко распространены в программировании, монады, описанные в этой статье, технически являются тем, что теоретики категорий назвали бы сильными монадами . [3]
  2. ^ Перейти обратно: а б Конкретную мотивацию Maybe можно найти в (Hutton 2016). [7]
  3. ^ Перейти обратно: а б Хаттон резюмирует bindкоторый, если задан тип a , который может потерпеть неудачу, и отображение a b , которое может потерпеть неудачу, дает результат b , который может потерпеть неудачу. (Хаттон, 2016) [7]
  4. ^ Перейти обратно: а б (Хаттон 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. ^ Перейти обратно: а б В этом случае bind вставил в string где раньше только integerбыл; то есть программист построил дополнение : кортеж (x,s), обозначенный int * string в псевдокоде § выше .
  17. ^ Алгебраически отношения между двумя (некоммутативными) аспектами моноида напоминают отношения почти полукольца , и некоторые аддитивные монады действительно квалифицируются как таковые. Однако не все аддитивные монады удовлетворяют законам распределения даже почти полукольца. [33]
  18. ^ В Haskell расширение на самом деле определяется с заменой входных данных, но поскольку в этой статье не используется каррирование, оно определяется здесь как точный аналог связывания .

Ссылки [ править ]

  1. ^ Перейти обратно: а б с д Это ж г О'Салливан, Брайан; Герцен, Джон; Стюарт, Дон (2009). «Монады» . Реальный мир Haskell . Севастополь, Калифорния: O'Reilly Media. глава 14. ISBN  978-0596514983 .
  2. ^ Перейти обратно: а б Уодлер, Филип (июнь 1990 г.). Понимание монад . Конференция ACM по LISP и функциональному программированию. Ницца, Франция. CiteSeerX   10.1.1.33.5381 .
  3. ^ Перейти обратно: а б с Моджи, Эудженио (1991). «Представления о вычислениях и монадах» (PDF) . Информация и вычисления . 93 (1): 55–92. CiteSeerX   10.1.1.158.5275 . дои : 10.1016/0890-5401(91)90052-4 .
  4. ^ Перейти обратно: а б с д Это Уодлер, Филип (январь 1992 г.). Сущность функционального программирования . 19-й ежегодный симпозиум ACM по принципам языков программирования. Альбукерке, Нью-Мексико. CiteSeerX   10.1.1.38.9516 .
  5. ^ Перейти обратно: а б Худак, Павел ; Петерсон, Джон; Фазель, Джозеф (1999). «О монадах» . Небольшое введение в Haskell 98 . глава 9.
  6. ^ Перейти обратно: а б Ответ CA McCann (23 июль 2010, в 23:39) Как и почему работает монада Haskell Cont?
  7. ^ Перейти обратно: а б с Грэм Хаттон (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-е исправленное изд.). Кафедра компьютерных наук Берлинского технического университета. CiteSeerX   10.1.1.40.2748 .
  22. ^ Moggi, Eugenio (June 1989). Computational lambda-calculus and monads (PDF). Fourth Annual Symposium on Logic in computer science. Pacific Grove, California. 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 Получено 20 ноября.
  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-Publishers. стр. 100-1 166–181. дои : 10.1007/978-3-642-29822-6_15 . ISBN  978-3-642-29822-6 .
  36. ^ Уусталу, Тармо; Давай, Жара (июль 2005 г.). Хорват, Золтан (ред.). Сущность программирования потоков данных (PDF) . Первая летняя школа Центральноевропейского функционального программирования. Конспекты лекций по информатике. Том. 4164. Будапешт, Венгрия: Springer-Verlag. стр. 100-1 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 по функциональному программированию. Нара, Япония: Ассоциация вычислительной техники. стр. 100-1 476–489. дои : 10.1145/2951913.2951939 . ISBN  978-1-4503-4219-3 .

Внешние ссылки [ править ]

Ссылки на HaskellWiki:

  • « Все о монадах » (первоначально Джефф Ньюберн) — подробное обсуждение всех распространенных монад и того, как они работают в Haskell; включает аналогию с «механизированной сборочной линией».
  • « Типклассопедия » (первоначально Брент Йорги) — подробное описание того, как взаимодействуют ведущие классы типов в Haskell, включая монады.

Учебники:

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