Почему побочные эффекты смоделированы как монады в Haskell?

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

Я подразумеваю, что монада является просто интерфейсом с 4 операциями, поэтому чем обоснование было к моделированию побочных эффектов в ней?

164
задан Viet 26 July 2017 в 05:19
поделиться

5 ответов

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

Итак, для нечистой функции

f' :: Int -> Int

мы добавляем RealWorld к рассмотрению

f :: Int -> RealWorld -> (Int, RealWorld)
-- input some states of the whole world,
-- modify the whole world because of the side effects,
-- then return the new world.

, тогда f снова чисто. Мы определяем параметризованный тип данных IO a = RealWorld -> (a, RealWorld) , поэтому нам не нужно набирать RealWorld так много раз

f :: Int -> IO Int

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

Эти нечистые функции бесполезны, если мы не можем связать их вместе. Рассмотрим

getLine :: IO String               = RealWorld -> (String, RealWorld)
getContents :: String -> IO String = String -> RealWorld -> (String, RealWorld)
putStrLn :: String -> IO ()        = String -> RealWorld -> ((), RealWorld)

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

printFile :: RealWorld -> ((), RealWorld)
printFile world0 = let (filename, world1) = getLine world0
                       (contents, world2) = (getContents filename) world1 
                   in  (putStrLn contents) world2 -- results in ((), world3)

Здесь мы видим шаблон: функции вызываются следующим образом:

...
(<result-of-f>, worldY) = f worldX
(<result-of-g>, worldZ) = g <result-of-f> worldY
...

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

(~~~) :: (IO b) -> (b -> IO c) -> IO c

(~~~) ::      (RealWorld -> (b, RealWorld))
      -> (b -> RealWorld -> (c, RealWorld))
      ->       RealWorld -> (c, RealWorld)
(f ~~~ g) worldX = let (resF, worldY) = f worldX in
                        g resF worldY

тогда мы могли бы просто написать

printFile = getLine ~~~ getContents ~~~ putStrLn

, не касаясь реального мира.


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

upperCase :: String -> String

, но чтобы воплотить его в жизнь, он должен вернуть строку ввода-вывода . Такую функцию легко поднять:

impureUpperCase :: String -> RealWorld -> (String, RealWorld)
impureUpperCase str world = (upperCase str, world)

это можно обобщить:

impurify :: a -> IO a

impurify :: a -> RealWorld -> (a, RealWorld)
impurify a world = (a, world)

так, чтобы impureUpperCase = impurify.upperCase , и мы можем написать

printUpperCaseFile = 
    getLine ~~~ getContents ~~~ (impurify . upperCase) ~~~ putStrLn

(Примечание: обычно мы пишем getLine ~~~ getContents ~~~ (putStrLn. upperCase) )


Теперь давайте посмотрим, что мы сделали:

  1. Мы определили оператор (~~~) :: IO b -> (b -> IO c) -> IO c , который связывает две нечистые функции вместе
  2. Мы определили функцию impurify :: a -> IO a , который преобразует чистое значение в нечистое.

Теперь мы производим идентификацию (>> =) = (~~~) и return = impurify , и видите? У нас есть монада.


(Чтобы проверить, действительно ли это монада, должно быть выполнено несколько аксиом:

(1) return a >> = f = fa

  impurify a               = (\world -> (a, world))
 (impurify a ~~~ f) worldX = let (resF, worldY) = (\world -> (a, world)) worldX 
                             in f resF worldY
                           = let (resF, worldY) =            (a, worldX))       
                             in f resF worldY
                           = f a worldX

(2) f >> = return = f

  (f ~~~ impurify) a worldX = let (resF, worldY) = impuify a worldX 
                              in f resF worldY
                            = let (resF, worldY) = (a, worldX)     
                              in f resF worldY
                            = f a worldX

(3) f >> = (\ x -> gx >> = h) = (f >> = g) >> = h

Упражнение.)

{{1} }
286
ответ дан 23 November 2019 в 21:12
поделиться

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

В большинстве языков программирования вы выполняете операции ввода / вывода. В Haskell представьте, что вы пишете код не для выполнения операций, а для генерации списка операций, которые вы хотели бы выполнить.

Монады - это просто красивый синтаксис именно для этого.

Если вы хотите знать, почему монады отличаются от чего-то еще, я полагаю, ответ состоит в том, что они являются лучшим функциональным способом представления ввода-вывода, о котором люди могли думать, когда создавали Haskell.

4
ответ дан 23 November 2019 в 21:12
поделиться

Как я Чтобы понять это, некто по имени Эудженио Моджи первым заметил, что ранее неясная математическая конструкция, называемая «монада», может использоваться для моделирования побочных эффектов на компьютерных языках и, следовательно, для определения их семантики с использованием лямбда-исчисления. Когда разрабатывался Haskell, существовали различные способы моделирования нечистых вычислений (подробности см. В статье Саймона Пейтона Джонса о «волосяной рубашке» ), но когда Фил Вадлер представил монады, быстро стало очевидно, что это были монады. Ответ. И остальное уже история.

13
ответ дан 23 November 2019 в 21:12
поделиться

AFAIK, причина в том, чтобы иметь возможность включить проверку побочных эффектов в систему типов. Если вы хотите узнать больше, послушайте эти эпизоды SE-Radio: Эпизод 108: Саймон Пейтон Джонс о функциональном программировании и Haskell Эпизод 72: Эрик Мейджер о LINQ

3
ответ дан 23 November 2019 в 21:12
поделиться

Может ли кто-нибудь подсказать, почему нечистые вычисления в Haskell моделируются как монады?

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

Это означает, что в конечном итоге вам придется иметь некоторый тип IO a, который моделирует нечистые вычисления. Затем вам нужно знать способы комбинирования этих вычислений, из которых применять последовательно (>>=) и поднимать значение (return) являются наиболее очевидными и основными.

С помощью этих двух вы уже определили монаду (даже не думая о ней);)

Кроме того, монады обеспечивают очень общие и мощные абстракции, поэтому многие виды потока управления могут быть удобно обобщены в монадические функции, такие как sequence, liftM или специальный синтаксис, что делает нечистоту не таким уж особым случаем.

См. подробнее монады в функциональном программировании и типизация уникальности (единственная известная мне альтернатива).

8
ответ дан 23 November 2019 в 21:12
поделиться
Другие вопросы по тегам:

Похожие вопросы: