Вопрос

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

Это то, где я сейчас нахожусь.Пожалуйста, поправьте меня, где я ошибаюсь.

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

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

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

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

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

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

Это было полезно?

Решение

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

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

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

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

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

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

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

Другие советы

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

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

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

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

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

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

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

Однако это очень специфично для монады ввода-вывода, а не для монад в целом.

Ниже приведено определение типа-class 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 определяет свой собственный >>= функция.Вот пример из type-instance 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

Без сахара обозначение do становится:

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

Типы действительно выстраиваются в очередь, как ни странно.Вот как.

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

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

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

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

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

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

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.Это не случайное совпадение.Разумная реализация "привязки" выглядит следующим образом:

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

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

join = (>>= id)

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

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

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

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top