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

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

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

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

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

Ответы [ 19 ]

7 голосов
/ 01 декабря 2012

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

Монада - это моноид в категории эндофункторов.

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

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

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

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

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

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

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

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

Итак:

return :: a -> m a

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

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

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

m >>= f >>= g

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

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

Тип (>>) может быть светящимся. Это

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

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

m = do x <- someQuery
       someAction x
       theNextAction
       andSoOn

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

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

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

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

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

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

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


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

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

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

Foo :: m (m a) <-> (m. M) a

3 голосов
/ 16 февраля 2014

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

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

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

Используемые здесь типы - это «типы с контекстом». Это значение с тегом. Цепные функции должны принимать «голое значение» и возвращать помеченный результат. Одной из обязанностей >> = является извлечение обнаженного значения из его контекста. Также есть функция return, которая принимает голое значение и помещает его с тегом.

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

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

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

-- tagged value
val1 = Just 160 

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

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

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

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

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

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

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

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

И это будет использоваться так:

print (runMyMonad (Just 160) myArray1)
2 голосов
/ 23 декабря 2012

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

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

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

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

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

2 голосов
/ 21 апреля 2019

Простое объяснение Монад с примером из практики Marvel: здесь .

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

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

enter image description here

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

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

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

Функция "bind" определена для конкретной F .

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

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

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

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

  • "random" ( Range => Random [Int] )
  • «print» ( String => IO [()] )
  • "попробуй ... поймай" и т. Д.
1 голос
/ 25 апреля 2010

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

Интервью Джеффри Сновера с Эриком Мейером становится более подробным.

1 голос
/ 30 ноября 2016

С точки зрения ОО, монада - это свободный контейнер.

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

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

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

В качестве грубого примера,

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

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

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

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

1 голос
/ 08 марта 2015

См. Мой ответ на "Что такое монада?"

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

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

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

#include <iostream>
#include <string>

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

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

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

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

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

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

int main()
{
    V x;
    M<T> m = feed(f, feed(g, wrap(x)));
    std::cout << m.messages;
}
0 голосов
/ 05 апреля 2013

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

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

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

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

0 голосов
/ 30 марта 2018

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

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

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

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

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

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

...