Haskell에서 I/O가 동작하는 방식을 간단한 예제와 함께 설명하고, putStrLn, getLine, (>>=), return 같은 핵심 개념을 소개합니다.
Haskell에서 I/O를 다루는 방식은 입문자들이 익숙한 방식과 매우 다를 수 있지만, 사실 몇 가지 간단한 규칙을 따를 뿐입니다. 여기서 그 규칙들을 설명해 보겠습니다.
예를 들어, 어떤 프로그램 어딘가에서 putStrLn을 "Hello"에 적용하면 터미널에 한 줄의 텍스트가 곧바로 찍히기를 기대할 수도 있습니다. 실제로는 아무 일도 일어나지 않습니다 대신 그 식은 "Hello"를 출력하는 동작(action)을 나타내는 값으로 평가될 뿐입니다. 이 식을 평가한다고 해서 그 동작이 실제로 수행되지는 않습니다.
putStrLn "Hello"와 같은 I/O 동작은 그저 평범한 값일 뿐입니다. 즉, 이 값들을 변수에 넣을 수도 있고, 리스트에 저장할 수도 있으며, 함수에 인자로 넘길 수도 있습니다. 다시 말해, I/O 동작은 일급 값입니다.
그렇다면 어떤 동작을 평가하는 것만으로는 수행되지 않는다면, 실제로는 어떻게 동작을 수행하게 만들까요? 정답은 간단합니다. 그 동작이 main이 되게 하면 됩니다:
haskellmain :: IO () main = putStrLn "Hello"
프로그램을 실행하면 Haskell 런타임이 main의 값을 가져와(main은 반드시 I/O 동작이어야 합니다) 그것을 수행합니다. 지금까지 살펴본 이 "I/O를 위한 레시피"만 보면, 우리는 한 번에 딱 하나의 동작만 수행할 수 있을 것처럼 보입니다.
그렇다면 두 개의 동작을 수행하려면 어떻게 해야 할까요? 두 개의 I/O 동작을 하나의 동작으로 결합하는 방법이 있습니다. Haskell 코드를 이해할 때 타입을 보는 것이 매우 유용하므로, 먼저 유용한 두 동작의 타입부터 보겠습니다:
haskellputStrLn :: String -> IO () getLine :: IO String
첫 번째 줄은 이렇게 읽을 수 있습니다. "putStrLn은 문자열을 하나 받아서, 수행되었을 때 ()²를 만들어 내는 동작을 돌려주는 함수이다." 두 번째 줄은 "getLine은 수행되었을 때 문자열을 만들어 내는 동작이다"라고 읽으면 됩니다.
두 동작을 결합하는 함수는 "bind"라 불리며, Haskell에서는 중위 연산자 ">>="로 씁니다. 타입은 다음과 같습니다:
haskell(>>=) :: IO a -> (a -> IO b) -> IO b
bind는 두 개의 인자(왼쪽 피연산자와 오른쪽 피연산자)를 받아서 하나의 합성된 동작을 돌려줍니다. 이 새 동작의 의미는 다음과 같습니다.
a의 값을 만들어 냅니다.이 새로운 재료 덕분에, 예를 들어 읽기와 출력이라는 두 가지 일을 하는 동작을 정의할 수 있습니다:
haskellmain :: IO () main = getLine >>= (\line -> putStrLn (reverse line))
이 예제는 터미널에서 한 줄을 읽어와서, 그 문자열을 뒤집은 뒤, 다시 출력합니다. 마지막으로, 동작들을 조합할 때 유용한 다른 함수가 하나 더 있습니다. 이름은 return이고 타입은 다음과 같습니다:³
haskellreturn :: a -> IO a
즉, a를 하나 받아서, 수행되었을 때 a를 만들어 내는 동작을 돌려주는 함수입니다. 이 동작은 실제로 아무런 I/O도 수행하지 않으며, 만들어 내는 값은 인자로 넘겨준 값 그대로입니다. return으로 만든 동작은 여러 중간 결과들을 하나의 더 큰 결과로 모을 때, 동작 시퀀스의 마지막 단계로 자주 사용됩니다. 예를 들면 다음과 같습니다:
haskellgetLinePair :: IO (String, String) getLinePair = getLine >>= (\x -> getLine >>= (\y -> return (x, y)))
이 동작의 의미는 먼저 터미널에서 첫 번째 줄을 읽고, 그 다음 두 번째 줄을 읽는 것입니다. 이 동작의 결과는 첫 번째 줄과 두 번째 줄로 이루어진 쌍(pair)입니다.
이것이 전부입니다. 다음 단계로는 "do 표기법"에 대해 읽어 보거나, System.IO 패키지 문서를 훑어보는 것이 좋겠습니다. 이 튜토리얼이 마음에 들었거나 궁금한 점이 있다면, 아래에 댓글을 남겨 주세요!
교정을 도와준 @kajgo, @ricli85에게 감사드립니다!
--raek