Пусть функция имеет побочные эффекты. Если мы берем все эффекты, которые он производит в качестве входных и выходных параметров, то функция чиста для внешнего мира.
Таким образом, для нечистой функции
f' :: Int -> Int
мы добавим RealWorld к рассмотрению
f :: Int -> RealWorld -> (Int, RealWorld)
-- input some states of the whole world,
-- modify the whole world because of the a side effects,
-- then return the new world.
затем f
чисто снова. Определим параметризованным тип данных IO a = RealWorld -> (a, RealWorld)
, поэтому нам не нужно набирать RealWorld столько раз
f :: Int -> IO Int
Для программиста, Обращение с RealWorld непосредственно слишком опасно — в частности, если программист получает в свои руки значение типа RealWorld, они могут попробовать копия это, что в принципе невозможно.(Подумайте о попытке скопировать всю файловую систему, например, где бы вы выразились?) Поэтому наше определение IO также инкапсулирует состояния всего мира.
Эти нечистые функции бесполезны, если мы не можем связать их вместе. Рассмотрите
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
Но сделать это в реальный мир, он должен вернуть IO 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)
)
Теперь давайте посмотрим, что мы сделали:
- Мы определили оператор
(~~~) :: IO b -> (b -> IO c) -> IO c
который цепочки две нечистых функций вместе
- Мы определили функцию
impurify :: a -> IO a
, которая преобразует чистое значение для нечистых.
Теперь мы делаем идентификацию (>>=) = (~~~)
и return = impurify
и видим? У нас есть монада.
(Для того, чтобы проверить, является ли это на самом деле монада есть несколько аксиом должны быть удовлетворены:
(1) return a >>= f = f a
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 -> g x >>= h) = (f >>= g) >>= h
Упражнение.)
Монасты просто определяют две операции. – Dario
, но как насчет возврата и сбоя? (кроме (>>) и (>> =)) – bodacydo
Две операции: 'return' и' (>> =) '. 'x >> y' совпадает с' x >> = \\ _ -> y' (т. е. он игнорирует результат первого аргумента). Мы не говорим о 'fail'. – porges