Подтвердить что ты не робот

Является ли "цепочка операций" "единственной" вещью, решаемой классом Монады?

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

После чтения многих ссылок (см. ниже), Я пришел к выводу, что на самом деле класс монады должен решать только только, но большой и решающий, проблема: "цепочка" функций по типам с контекстом. Следовательно, знаменитое предложение "монады - программируемые точки с запятой". Фактически, монаду можно рассматривать как массив функций с вспомогательными операциями.

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

Я понимаю, что класс монады сам по себе решает только цепочку операторов, потому что, в основном, он только задает свои экземпляры типа иметь bind >>= и return, и сообщить нам, как они должны себя вести. И в качестве бонуса компилятор помогает кодированию, предоставляя нотацию do для монадических типов.

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

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

И вы решаете проблему цепочки, если у вас есть каждый тип экземпляра класса монады. Для того, чтобы цепочка работала, вам нужно >>= только с точной подписью, которую она имеет, и никаким другим. (См. этот вопрос).

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

Затем я задаю два вопроса специалистам Haskell:

  • Конкретный вопрос: есть ли какая-либо другая проблема, которую класс монады решает, если (если оставить в стороне монадические классы)? Если да, то как он сравнивается по отношению к проблеме цепочки операций?
  • Необязательный общий вопрос: мои выводы правильно, я что-то не понимаю?

Ссылки

Учебники

Вопросы и ответы StackOverflow

4b9b3361

Ответ 1

Вы определенно настроены на то, как вы заявляете это: есть много вещей, которые Monad означает, и вы отлично отделили их.

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


Чтобы сделать точку более понятной, единственное место, где вы имеете дело с Монадой, но не с какой-либо конкретной монадой, - это если вы определяете что-то с полиморфизмом, ограниченным как Monad. Это повторяется в модуле Control.Monad, поэтому мы можем рассмотреть некоторые примеры оттуда.

sequence     :: [m a] -> m [a]
forever      :: m a   -> m b
foldM        :: (a -> b -> m a) -> a -> [b] -> m a

Сразу же мы можем выбросить sequence как особенность Monad, так как соответствующая функция в Data.Traversable, sequenceA имеет тип, несколько более общий, чем Applicative f => [f a] -> f [a]. Это должно быть ясным индикатором того, что Monad не единственный способ упорядочить вещи.

Аналогично, мы можем определить foreverA следующим образом:

foreverA :: Applicative f => f a -> f b
foreverA f = flip const <$> f <*> foreverA f

Так больше способов упорядочить типы Monad. Но мы сталкиваемся с проблемами с foldM

foldM             :: (Monad m) => (a -> b -> m a) -> a -> [b] -> m a
foldM _ a []      =  return a
foldM f a (x:xs)  =  f a x >>= \fax -> foldM f fax xs

Если мы попытаемся перевести это определение в стиль Applicative, мы можем написать

foldA             :: (Applicative f) => (a -> b -> f a) -> a -> [b] -> f a
foldA _ a []      =  pure a
foldA f a (x:xs)  = foldA f <$> f a x <*> xs

Но Haskell по праву жалуется, что это не typecheck - каждый рекурсивный вызов foldA пытается поместить другой "слой" из f в результат. С Monad мы могли бы join те слои вниз, но Applicative слишком слабые.


Итак, как это перевести на Applicative, ограничивая нас выбором времени исполнения? Ну, именно то, что мы выражаем с помощью foldM, монадическое вычисление (a -> b -> m a), которое зависит от его аргумента a, является результатом предварительного монадического вычисления. Такого рода вещи просто не имеют никакого значения в более чисто последовательном мире Applicative.

Ответ 2

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

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

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

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