Можно ли считать монады на Haskell использованием и возвращением скрытого параметра состояния? - PullRequest
9 голосов
/ 12 мая 2010

Я не понимаю точную алгебру и теорию, лежащую в основе монад Хаскелла. Тем не менее, когда я думаю о функциональном программировании в целом, у меня складывается впечатление, что состояние будет смоделировано путем принятия исходного состояния и создания его копии для представления следующего состояния. Это похоже на то, когда один список добавляется к другому; ни один список не изменяется, но третий список создается и возвращается.

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

Другими словами, если у нас есть этот код:

main = do
    putStrLn "Enter your name:"
    name <- getLine
    putStrLn ( "Hello " ++ name )

... можно ли думать о монаде IO и синтаксисе "do" как о представлении этого стиля кода?

putStrLn :: IOState -> String -> IOState
getLine :: IOState -> (IOState, String)
main :: IOState -> IOState

-- main returns an IOState we can call "state3"
main state0 = putStrLn state2 ("Hello " ++ name)
    where (state2, name) = getLine state1
        state1 = putStrLn state0 "Enter your name:"

Ответы [ 5 ]

18 голосов
/ 12 мая 2010

Нет, это не то, что монады вообще делают. Тем не менее, ваша аналогия на самом деле в точности верна в отношении типа данных State s a, который оказывается a монадой. State определяется следующим образом:

newtype State s a = State { runState :: s -> (a, s) }

... где переменная типа s - это значение состояния, а a - это "обычное" значение, которое вы используете. Таким образом, значение в «Монаде состояния» - это просто функция от начального состояния до возвращаемого значения и конечного состояния. Монадический стиль, применяемый к State, не делает ничего, кроме автоматической передачи значения состояния через последовательность функций.

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

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

Тем не менее, другие монады не обязательно имеют какое-либо отношение к состоянию потоков, функциям секвенирования или к чему-либо еще. Операции, необходимые для того, чтобы быть монадой, являются невероятно общими и абстрактными. Например, использование Maybe или Either в качестве монад позволяет использовать функции, которые могут возвращать ошибки, при этом обработка монадического стиля выходит из вычисления, когда ошибка происходит так же, как State передает значение состояния. Использование списков в качестве монады дает вам недетерминированность , позволяя вам одновременно применять функции к нескольким входам и видеть все возможные выходы, причем монадический стиль автоматически применяет функцию к каждому аргументу и собирает все выходы.

8 голосов
/ 12 мая 2010

Поэтому допустимо ли думать о монадических операциях как о неявном взятии объекта начального состояния в качестве параметра и неявном возврате объекта конечного состояния?

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

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

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

4 голосов
/ 12 мая 2010

Не монады вообще, но для монады ввода-вывода, да - фактически тип IO a часто определяется как тип функции RealWorld -> (RealWorld, a). Таким образом, с этой точки зрения, тип десгурирования putStrLn равен String -> RealWorld -> (RealWorld, ()), а getChar равен RealWorld -> (RealWorld, Char) - и мы применяем его только частично, с монадным связыванием, который позаботится о полной его оценке и передаче реального мира. (Библиотека ST GHC на самом деле включает в себя очень реальный тип RealWorld , хотя он описан как «глубоко магический», а не для реального использования.)

Однако есть много других монад, у которых нет этого свойства. Нет никакого RealWorld, передаваемого, например, с помощью монад [1,2,3,4] или Just "Hello".

3 голосов
/ 12 мая 2010

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

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

Экстремальный пример (просто теоретический пример, который вам вряд ли захочется сделать) может быть следующим: C допускает нечистые вычисления, поэтому вы можете определить тип, представляющий фрагмент кода C. Затем вы можете написать интерпретатор, который берет одну из этих структур C и интерпретирует ее. Все монады такие же, хотя обычно намного проще, чем интерпретатор Си. Но это представление более мощное, потому что оно также объясняет монады, которые не предназначены для обхода скрытого состояния.

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

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

1 голос
/ 12 мая 2010

Я предпочитаю думать о монадах как об объектах, представляющих отложенные действия (runXXX, main) с результатом, который можно комбинировать в соответствии с этим результатом.

return "somthing"
actionA >>= \x -> makeActionB x

И эти действия не обязательно должны быть заполнены государством. То есть Вы можете думать о монаде построения функции следующим образом:

instance Monad ((->) a) where
    m >>= fm = \x -> fm (m x) x
    return = const

sqr = \x -> x*x
cube = \x -> x*x*x

weird = do
    a <- sqr
    b <- cube
    return (a+b)
...