함수형 프로그래밍 언어가 상태를 저장할 수없는 경우 사용자로부터 입력을 읽거나 (이를 "저장"하는 방법) 그 문제에 대한 데이터를 저장하는 것과 같은 간단한 작업을 어떻게 수행합니까?
수집 한대로 함수형 프로그래밍에는 상태가 없지만 그렇다고 데이터를 저장할 수 없다는 의미는 아닙니다. 차이점은 내가 (Haskell) 문장을 다음과 같이 쓰면
let x = func value 3.14 20 "random"
in ...
의 값 x
은 항상 동일 하다는 것을 보장 합니다 ...
. 아무것도 변경할 수 없습니다. 마찬가지로 함수 f :: String -> Integer
(문자열을 가져와 정수를 반환하는 함수)가있는 경우 f
인수를 수정하거나 전역 변수를 변경하거나 파일에 데이터를 쓰는 등의 작업을 수행하지 않을 것이라고 확신 할 수 있습니다 . sepp2k가 위의 주석에서 말했듯이,이 불변성은 프로그램에 대한 추론에 정말 도움이됩니다. 데이터를 접고, 회전시키고, 절단하는 함수를 작성하고, 함께 연결할 수 있도록 새 복사본을 반환하고, 이러한 함수 호출 중 "유해한"작업을 수행 할 수 있습니다. 당신은 그것이 x
항상 있다는 것을 알고 있으며 x
, 누군가 x := foo bar
가 선언 사이 어딘가에 썼다고 걱정할 필요가 없습니다.x
불가능하기 때문입니다.
이제 사용자의 입력을 읽으려면 어떻게해야합니까? KennyTM가 말했듯이, 불순한 함수는 전 세계를 인수로 전달하고 그 결과와 세계를 모두 반환하는 순수 함수라는 아이디어입니다. 물론 실제로이 작업을하고 싶지는 않습니다. 한 가지는 끔찍하게 투박하고 다른 한 가지는 동일한 월드 객체를 재사용하면 어떻게 될까요? 그래서 이것은 어떻게 든 추상화됩니다. Haskell은 IO 유형으로 처리합니다.
main :: IO ()
main = do str <- getLine
let no = fst . head $ reads str :: Integer
...
이것은 main
아무것도 반환하지 않는 IO 액션 임을 알려줍니다 . 이 동작을 실행하는 것은 Haskell 프로그램을 실행한다는 의미입니다. 규칙은 IO 유형이 IO 작업을 벗어날 수 없다는 것입니다. 이 맥락에서 우리는 do
. 따라서 두 가지 방식으로 생각할 수 getLine
있는를 반환합니다 IO String
. 첫째, 실행시 문자열을 생성하는 작업으로; 둘째, 불순하게 획득 되었기 때문에 IO에 의해 "오염 된"문자열입니다. 첫 번째가 더 정확하지만 두 번째가 더 도움이 될 수 있습니다. (가) <-
소요 String
의 외부 IO String
에 저장 그것을 str
우리가 IO 작업에있어 이후 -하지만, 우리는 그래서 "탈출"할 수 없습니다, 그것은 백업 포장해야합니다. 다음 줄은 정수 ( reads
) 읽기를 시도 하고 첫 번째 성공한 일치 항목 (fst . head
); 이것은 모두 순수 (IO 없음)이므로 let no = ...
. 우리는 모두 사용할 수 있습니다 no
및 str
에를 ...
. 따라서 우리는 불순한 데이터를 저장 (에서 한 getLine
에 str
) 순수 데이터 ( let no = ...
).
IO 작업을위한이 메커니즘은 매우 강력합니다. 프로그램의 순수하고 알고리즘적인 부분을 불순한 사용자 상호 작용 측면에서 분리하고이를 유형 수준에서 적용 할 수 있습니다. 귀하의 minimumSpanningTree
기능은 가능성이 코드에서 다른 곳에서 뭔가를 변경하거나 사용자에게 메시지를 작성하고, 등등 수 없습니다. 안전합니다.
이것이 Haskell에서 IO를 사용하기 위해 알아야 할 전부입니다. 그게 전부라면 여기서 멈출 수 있습니다. 그러나 그것이 작동 하는 이유 를 이해하고 싶다면 계속 읽으십시오. (또한이 내용은 Haskell에만 해당됩니다. 다른 언어는 다른 구현을 선택할 수 있습니다.)
그래서 이것은 아마도 약간의 속임수처럼 보였고 어떻게 든 순수한 Haskell에 불순물을 추가했습니다. 그러나 그렇지 않습니다. 순수 Haskell 내에서 IO 유형을 완전히 구현할 수 있습니다 (를 제공하는 한 RealWorld
). 아이디어는 이것이다 : IO 액션 IO type
은 RealWorld -> (type, RealWorld)
실제 세계를 취하고 유형의 객체 type
와 수정 된 객체를 모두 반환 하는 함수와 동일 합니다 RealWorld
. 그런 다음 몇 가지 함수를 정의하여 정신 나간 일없이이 유형을 사용할 수 있습니다.
return :: a -> IO a
return a = \rw -> (a,rw)
(>>=) :: IO a -> (a -> IO b) -> IO b
ioa >>= fn = \rw -> let (a,rw') = ioa rw in fn a rw'
첫 번째는 우리가 아무것도하지 않는 IO 액션에 대해 이야기 할 수있게합니다 : return 3
IO 액션은 실제 세계를 쿼리하지 않고 3
. >>=
"bind"라고 발음 하는 연산자를 사용하면 IO 작업을 실행할 수 있습니다. IO 액션에서 값을 추출하고 함수를 통해 실제 세계에 전달하고 결과 IO 액션을 반환합니다. >>=
IO 작업의 결과는 절대 탈출 할 수 없다는 규칙 을 적용합니다.
그런 다음 위 main
를 다음과 같은 일반적인 기능 응용 프로그램 집합으로 바꿀 수 있습니다 .
main = getLine >>= \str -> let no = (fst . head $ reads str :: Integer) in ...
Haskell 런타임 main
은 초기 RealWorld
에서 시작 하여 설정되었습니다! 모든 것이 순수하고 멋진 구문이 있습니다.
[ 편집 : @Conal이 지적했듯이 이것은 실제로 Haskell이 IO를 수행하는 데 사용하는 것이 아닙니다. 이 모델은 동시성을 추가하거나 실제로 IO 작업 중에 세계가 변경되는 방식을 추가하면 중단되므로 Haskell이이 모델을 사용하는 것은 불가능합니다. 순차 계산에만 정확합니다. 따라서 Haskell의 IO는 약간의 닷지 일 수 있습니다. 그렇지 않더라도 확실히 이렇게 우아하지는 않습니다. @Conal의 관찰에 따르면, Tackling the Awkward Squad [pdf] , 섹션 3.1 에서 Simon Peyton-Jones의 말을 참조하십시오 . 그는 이러한 라인을 따라 대체 모델에 해당 할 수있는 것을 제시하지만 복잡성 때문에이를 삭제하고 다른 방법을 사용합니다.]
다시 말하지만, 이것은 IO와 일반적으로 변경 가능성이 하스켈에서 어떻게 작동하는지 (거의) 설명합니다. 경우 이 당신이 알고 싶은 모든 것입니다, 당신은 여기 읽는 중지 할 수 있습니다. 마지막으로 한 번의 이론을 원하신다면 계속 읽으십시오. 그러나이 시점에서 우리는 귀하의 질문과는 거리가 멀었습니다.
마지막으로 한 가지 :이 구조 ( return
and가 있는 매개 변수 유형) >>=
는 매우 일반적입니다. 그것은 모나드라고 불리며, do
표기법 return
, 그리고 >>=
그들 중 하나와 함께 작동합니다. 여기에서 보셨 듯이 모나드는 마법이 아닙니다. 마법의 전부는 do
블록이 함수 호출로 바뀌는 것입니다. RealWorld
유형은 우리가 어떤 마법을 볼 수있는 유일한 장소입니다. []
목록 생성자 인, 같은 유형 도 모나드이며 불순한 코드와 관련이 없습니다.
이제 모나드의 개념에 대한 모든 것을 (거의) 알고 있지만 (만족해야하는 몇 가지 법칙과 공식적인 수학적 정의를 제외하고) 직관이 부족합니다. 온라인에는 엄청나게 많은 모나드 자습서가 있습니다. 내가 좋아하는 이 하나 ,하지만 당신은 옵션이 있습니다. 그러나 이것은 아마도 당신에게 도움이되지 않을 것입니다 ; 직관을 얻는 유일한 방법은 그것들을 사용하고 적시에 몇 개의 튜토리얼을 읽는 것입니다.
그러나 IO를 이해하기 위해 직감이 필요하지는 않습니다 . 전체적으로 모나드를 이해하는 것은 매우 중요하지만 지금 당장 IO를 사용할 수 있습니다. 첫 번째 main
기능을 보여 드린 후에 사용할 수 있습니다 . IO 코드를 불순한 언어로 취급 할 수도 있습니다! 그러나 근본적인 기능적 표현이 있다는 것을 기억하십시오. 아무도 속임수를 쓰지 않습니다.
(추신 : 길이에 대해 죄송합니다. 조금 멀리갔습니다.)