Несколько вопросов о монадах в Хаскеле - PullRequest
11 голосов
/ 25 октября 2009

Я изучаю монады и у меня есть несколько вопросов.

Вот где я сейчас. Пожалуйста, поправьте меня, где я не прав.

  • Символ >>= является инфиксным оператором. Инфиксные операторы - это функции, которые принимают два аргумента (слева и справа) и возвращают значение.

  • Символ >>= называется оператором связывания и имеет подпись Monad m => m t -> (t -> m u) -> m u. Однако типы здесь не совпадают. Мы получаем значение типа m t, а вторым аргументом является функция, которая принимает t. (Я не вижу, как соединить точки.)

  • Это должно означать, что функция связывания каким-то образом способна удалить m из m t, чтобы получить t и передать его функции.

Вот мои вопросы:

  • Это возможность удалить m из m t, что возможно только внутри такого оператора связывания. У этого оператора связывания есть какие-то особые привилегии или что-то в этом роде?

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

Ответы [ 6 ]

12 голосов
/ 25 октября 2009

- это возможность удалить «M» из «M t», что возможно только внутри такого оператора связывания.

Ну, это, конечно, возможно внутри оператора связывания, так как его тип указывает:

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

Функция 'run' для вашей монады обычно может также делать это (чтобы вернуть чистое значение из ваших вычислений).

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

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

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

Это очень специфично для монады IO, но не для монад вообще.

10 голосов
/ 25 октября 2009

Способность удалять «M» из «M t», что возможно только внутри такого оператора связывания. У этого оператора связи есть какие-то особые привилегии или что-то в этом роде?

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

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

Особый случай - это монада IO, поскольку она не определяется модулем, а встроена в исполняющую систему / компилятор. Здесь компилятор знает о внутренних деталях своей реализации и предоставляет такие функции, как IO 's >>=. Реализации этих функций действительно имеют особые привилегии, поскольку они живут «вне программы», но это особый случай, и этот факт не должен быть заметен изнутри Haskell.

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

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

Обычно монада (в частности, это функция связывания) определяет способ, которым определенные функции должны быть объединены в более крупные функции. Этот метод объединения функций абстрагируется в монаде. Как именно это объединение работает или почему вы хотите объединить функции таким образом, не важно, монада просто определяет способ объединения определенных функций определенным образом. (См. Также , ответ на этот вопрос "Монады для программистов на C #" , где я в основном повторяю это несколько раз с примерами.)

5 голосов
/ 26 октября 2009

Ниже приведено определение класса типов Monad.

class  Monad m  where

    (>>=)       :: forall a b. m a -> (a -> m b) -> m b
    (>>)        :: forall a b. m a -> m b -> m b
    return      :: a -> m a
    fail        :: String -> m a

    m >> k      = m >>= \_ -> k
    fail s      = error s

Каждый экземпляр типа класса-класса Monad определяет свою собственную функцию >>=. Вот пример из экземпляра типа Maybe:

instance  Monad Maybe  where

    (Just x) >>= k      = k x
    Nothing  >>= _      = Nothing

    (Just _) >>  k      = k
    Nothing  >>  _      = Nothing

    return              = Just
    fail _              = Nothing

Как мы видим, потому что Maybe версия >>= специально определена для понимания экземпляра типа Maybe и потому что она определена в месте, которое имеет законный доступ к конструкторам данных data Maybe a Nothing и Just a, Maybe версия >>= может развернуть a в Maybe a и пропустить их через.

Чтобы проработать пример, мы могли бы взять:

x :: Maybe Integer
x = do a <- Just 5
       b <- Just (a + 1)
       return b

Обезвоженный, дотация становится:

x :: Maybe Integer
x = Just 5        >>= \a ->
    Just (a + 1)  >>= \b ->
    Just b

Что оценивается как:

  =                  (\a ->
    Just (a + 1)  >>= \b ->
    Just b) 5

  = Just (5 + 1)  >>= \b ->
    Just b

  =                  (\b ->
    Just b) (5 + 1)

  = Just (5 + 1)

  = Just 6
4 голосов
/ 26 октября 2009

Типы выстраиваются в линию, как ни странно. Вот как.

Помните, что монада также является функтором. Следующая функция определена для всех функторов:

fmap :: (Functor f) => (a -> b) -> f a -> f b

Теперь вопрос: действительно ли эти типы выстраиваются в линию? Ну да. Если задана функция от a до b, то если у нас есть среда f, в которой доступно a, у нас есть среда f, в которой b доступна.

По аналогии с силлогизмом:

(Functor Socrates) => (Man -> Mortal) -> Socrates Man -> Socrates Mortal

Теперь, как вы знаете, монада - это функтор, снабженный связыванием и возвратом:

return :: (Monad m) => a -> m a
(=<<) :: (Monad m) => (a -> m b) -> m a -> m b

Вы, возможно, не знаете, что это эквивалентно, это функтор, снабженный return и join:

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

Посмотрите, как мы снимаем m. С монадой m вы не всегда можете получить от m a до a, но вы всегда можете получить от m (m a) до m a.

Теперь посмотрим на первый аргумент (=<<). Это функция типа (a -> m b). Что происходит, когда вы передаете эту функцию fmap? Вы получаете m a -> m (m b). Итак, «отображение» над m a с помощью функции a -> m b дает вам m (m b). Обратите внимание, что это в точности соответствует типу аргумента join. Это не случайность. Разумная реализация «bind» выглядит так:

(>>=) :: m a -> (a -> m b) -> m b
x >>= f = join (fmap f x)

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

join = (>>= id)
2 голосов
/ 29 октября 2009

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

Это на самом деле немного более тонко, чем это. Монады позволяют нам моделировать последовательность в очень общем виде. Часто, когда вы разговариваете с экспертом по предметной области, вы обнаруживаете, что он говорит что-то вроде: «сначала мы пробуем X. Затем мы пробуем Y, а если это не сработает, то мы пробуем Z». Когда вы реализуете что-то подобное на обычном языке, вы обнаруживаете, что оно не подходит, поэтому вам нужно написать много дополнительного кода, чтобы охватить то, что эксперт по домену имел в виду под словом «тогда».

В Haskell вы можете реализовать это как монаду с переводом «затем» в оператор связывания. Так, например, я однажды написал программу, в которой элемент должен быть назначен из пулов в соответствии с определенными правилами. Для случая 1 вы взяли его из пула X. Если он был пустым, то вы перешли к пулу Y. Для случая 2 вы должны были взять его прямо из пула Y. И так для дюжины или около того случаев, включая некоторые, где вы брали наименее недавно использованный из пула X или Y. Я специально для этой работы написал специальную монаду, чтобы написать:

case c of
   1: do {try poolX; try poolY}
   2: try poolY
   3: try $ lru [poolX, poolY]

Это сработало очень хорошо.

Конечно, это включает в себя обычные модели секвенирования. Монада IO - это модель, которая есть во всех других языках программирования; Просто в Хаскеле это явный выбор, а не часть окружающей среды. Монада ST дает вам мутацию памяти ввода-вывода, но без фактического ввода и вывода. С другой стороны, монада State позволяет ограничить ваше состояние одним значением именованного типа.

Что-то действительно изгибающее мозг, см. в этом блоге о монаде отсталого состояния. Государство распространяется в направлении, противоположном «исполнению». Если вы думаете об этом как о монаде состояния, выполняющей одну инструкцию с последующей следующей, тогда «put» отправит значение состояния назад во времени на любой предшествующий «get». На самом деле происходит, когда устанавливается взаимно рекурсивная функция, которая завершается, только если нет парадоксов. Я не совсем уверен, где использовать такую ​​монаду, но она иллюстрирует точку зрения о том, что монады являются моделями вычислений.

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

2 голосов
/ 26 октября 2009

Я ОЧЕНЬ рекомендую вам прочитать (http://blog.sigfpe.com/2006/08/you-could-have-invented-monads-and.html).. Это дает разумную причину, по которой существуют монады.

...