Что такого особенного в Монаде?

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

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

Другое объяснение может состоять в том, что Haskell предоставляет синтаксис для монад (do-notation), но не для других структур, что означает, что программисты на Haskell (и, следовательно, исследователи функционального программирования) интуитивно тянутся к монадам, где более общая или конкретная (эффективная) функция будет работать так же.

6 ответов

Решение

Я подозреваю, что непропорционально большое внимание уделяется этому конкретному типу класса (Monad) над многими другими это в основном историческая случайность. Люди часто ассоциируют IO с Monad, хотя эти два являются независимо полезными идеями ( как обращение списка и бананы). Так как IO является волшебным (имеет реализацию, но не обозначение) и Monad часто ассоциируется с IOлегко впасть в магическое мышление о Monad,

(В сторону: сомнительно, IO даже это монада. Действуют ли законы монады? Что законы вообще значат для IOчто означает равенство? Обратите внимание на проблемную ассоциацию с государственной монадой.)

Если тип m :: * -> * имеет Monad Например, вы получаете Turing-полную композицию функций с типом a -> m b, Это фантастически полезное свойство. Вы получаете возможность абстрагировать различные потоки управления, полные по Тьюрингу, от определенных значений. Это минимальный шаблон композиции, который поддерживает абстрагирование любого потока управления для работы с типами, которые его поддерживают.

Сравните это с Applicative, например. Там вы получаете только шаблоны композиций с вычислительной мощностью, эквивалентной автомату. Конечно, это правда, что больше типов поддерживают композицию с более ограниченной мощностью. И действительно, когда вы ограничиваете доступную мощность, вы можете выполнять дополнительную оптимизацию. Эти две причины, почему Applicative класс существует и полезен. Но вещи, которые могут быть примерами Monad как правило, так что пользователи типа могут выполнять самые общие операции с типом.

Изменить: По многочисленным просьбам, вот некоторые функции, использующие Monad учебный класс:

ifM :: Monad m => m Bool -> m a -> m a -> m a
ifM c x y = c >>= \z -> if z then x else y

whileM :: Monad m => (a -> m Bool) -> (a -> m a) -> a -> m a
whileM p step x = ifM (p x) (step x >>= whileM p step) (return x)

(*&&) :: Monad m => m Bool -> m Bool -> m Bool
x *&& y = ifM x y (return False)

(*||) :: Monad m => m Bool -> m Bool -> m Bool
x *|| y = ifM x (return True) y

notM :: Monad m => m Bool -> m Bool
notM x = x >>= return . not

Объединение тех, кто с синтаксисом do (или необработанным >>= оператор) дает вам привязку имени, неопределенный цикл и полную логическую логику. Это общеизвестный набор примитивов, достаточный для полноты по Тьюрингу. Обратите внимание, как все функции были подняты для работы с монадическими значениями, а не с простыми значениями. Все монадические эффекты связаны только при необходимости - только эффекты из выбранной ветви ifM связаны в его окончательное значение. И то и другое *&& а также *|| игнорировать их второй аргумент, когда это возможно. И так далее..

Теперь эти сигнатуры типов могут не включать функции для каждого монадического операнда, но это всего лишь когнитивное упрощение. Не будет никакой семантической разницы, игнорируя основания, если все нефункциональные аргументы и результаты будут изменены на () -> m a, Пользователям просто удобнее оптимизировать эти когнитивные издержки.

Теперь давайте посмотрим, что происходит с этими функциями с Applicative интерфейс.

ifA :: Applicative f => f Bool -> f a -> f a -> f a
ifA c x y = (\c' x' y' -> if c' then x' else y') <$> c <*> x <*> y

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

whileA :: Applicative f => (a -> f Bool) -> (a -> f a) -> a -> f a
whileA p step x = ifA (p x) (whileA p step <$> step x) (pure x)

Ну, ладно, кажется, что все будет хорошо, за исключением того факта, что это бесконечный цикл, потому что ifA всегда будет выполнять обе ветви... За исключением того, что это даже не так близко. pure x имеет тип f a, whileA p step <$> step x имеет тип f (f a), Это даже не бесконечный цикл. Это ошибка компиляции. Давай еще раз попробуем..

whileA :: Applicative f => (a -> f Bool) -> (a -> f a) -> a -> f a
whileA p step x = ifA (p x) (whileA p step <*> step x) (pure x)

Хорошо стреляй. Даже не заходи так далеко. whileA p step имеет тип a -> f a, Если вы попытаетесь использовать его в качестве первого аргумента <*> хватается за Applicative экземпляр для конструктора верхнего типа, который (->) не f, Да, это тоже не сработает.

На самом деле единственная функция от моего Monad примеры, которые будут работать с Applicative интерфейс notM, Эта конкретная функция прекрасно работает только с Functor интерфейс, по сути. Остальные? Они терпят неудачу.

Конечно, следует ожидать, что вы можете написать код, используя Monad интерфейс, который вы не можете с Applicative интерфейс. В конце концов, он строго сильнее. Но что интересно, это то, что вы теряете. Вы теряете возможность составлять функции, которые меняют эффекты, которые они оказывают, основываясь на их входных данных. То есть вы теряете возможность писать определенные шаблоны потоков управления, которые сочетают функции с типами a -> f b,

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

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

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

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

Но да, монады чрезвычайно популярны. Часть этого - нотация do; Многие Monoids предоставляют экземпляры Monad, которые просто добавляют значения к работающему аккумулятору (по сути, неявный писатель). Библиотека blaze-html является хорошим примером. Я думаю, что причина в силе сигнатуры типа (>>=) :: Monad m => m a -> (a -> m b) -> m b, Хотя fmap и mappend полезны, они могут использовать довольно узкие ограничения. bind, однако, может выражать самые разные вещи. Это, конечно, канонизировано в монаде IO, возможно, лучший чисто функциональный подход к IO до потоков и FRP (и все еще полезен рядом с ними для простых задач и определения компонентов). Но он также обеспечивает неявное состояние (Reader/Writer/ST), что позволяет избежать передачи некоторых очень утомительных переменных. Особенно важны различные монады состояний, поскольку они гарантируют, что состояние является однопоточным, что позволяет перед изменением объединять структуры в чистом (не-IO) коде. Но у bind есть несколько более экзотических применений, таких как выравнивание вложенных структур данных (монады List и Set), которые весьма полезны вместо них (и я обычно вижу, что они используют desugared, явно вызывая liftM или (>>=), так что это не вопрос обозначений делать). Таким образом, хотя Functor и Monoid (и несколько более редкие Foldable, Alternative, Traversable и другие) предоставляют стандартизированный интерфейс для довольно простой функции, связывание Monad значительно более гибко.

Короче говоря, я думаю, что все ваши причины имеют определенную роль; популярность монад обусловлена ​​сочетанием исторической случайности (обозначения и позднее определение Applicative) и их сочетанием силы и общности (относительно функторов, моноидов и т. п.) и понятности (относительно стрелок).

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

В этом есть как положительная, так и отрицательная сторона: положительно, что вы можете выразить все те управляющие структуры, которые вы знаете по императивному программированию, и целую кучу их, которую вы не знаете. Я только недавно разработал монаду, которая позволяет вам повторно вводить вычисления где-то посередине с немного измененным контекстом. Таким образом, вы можете запустить вычисление, и если оно не удастся, просто попробуйте еще раз с немного скорректированными значениями. Более того, монадические действия являются первоклассными, и именно так вы создаете такие вещи, как циклы или обработка исключений. В то время как while примитивен в C в Haskell, на самом деле это просто обычная функция.

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

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

Что такого особенного в монадах?

Монадический интерфейс mainПретензия на известность в Haskell заключается в его роли в замене оригинального и громоздкого диалогового механизма ввода-вывода .

Что касается их статуса в формальном контексте расследования ... это просто повторение, казалось бы, циклического усилия, которому сейчас (октябрь 2021 г.) примерно полвека:

В 1960-х годах несколько исследователей начали работу над доказательствами программ. Были предприняты попытки доказать, что:

  • Программа была правильной.

  • Две программы с разным кодом давали одинаковые ответы при одинаковых входных данных.

  • Одна программа была быстрее другой.

  • Данная программа всегда будет завершаться.

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

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

Анатомия языков программирования , Элис Э. Фишер и Фрэнсис С. Гродзинский.

(выделено мной).

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

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

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

Другие вопросы по тегам