함수에 부작용이 있다고 가정하십시오. 입력 및 출력 매개 변수로 생성되는 모든 효과를 취하면 함수는 외부 세계에 순수합니다.
따라서 불순한 기능
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
다시 순수합니다. 매개 변수화 된 데이터 유형을 정의 type 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
, 볼? 모나드가 있습니다.
기술 노트
그것이 실제로 모나드임을 보장하기 위해 여전히 확인해야 할 몇 가지 공리가 있습니다.
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
f >>= return = f
(f ~~~ impurify) worldX = let (resF, worldY) = f worldX
in impurify resF worldY
= let (resF, worldY) = f worldX
in (resF, worldY)
= f worldX
f >>= (\x -> g x >>= h) = (f >>= g) >>= h
운동으로 떠났다.