하스켈의 IO, 하나의 통찰

ko생성일: 2025. 10. 18.갱신일: 2025. 10. 18.

하스켈에서 IO가 문(statement)을 모델링하는 방식과, 문과 표현식의 구분을 타입 수준에서 명시적으로 강제하는 효과를 설명한다. 또한 IO a를 부수효과를 수반할 수 있는 실행의 서술로 보는 관점을 제시하며, 내부적으로 상태 함수(s -> (a, s))로 볼 수 있음을 짚는다.

O의 블로그 -------- 블로그

2025년 5월 25일

제게는 IO가 하스켈에서 "문(statement)"이라는 일반적 개념을 모델링/표현하는 방식으로 보입니다. 여기서 문이란 행동/불순한 연산, 즉 부수효과를 수반하며 값의 반환이 있을 수도 없을 수도 있는 연산을 말합니다. 문이 왜 중요한지는 분명합니다. 문 덕분에 프로그램은 실제로 유용한 일을 할 수 있지만, 그 대가로 비결정성(세계 상태에 대한 변화가 실행마다 일관되지 않을 수 있음)과 순서 제약(효과가 특정 순서로 일어나야 함)을 감수해야 합니다.

주류 언어들에서는 문과 표현식을 의미 있게 구분하려는 노력이 거의 없곤 합니다(적어도 제 경험상). 그 결과, 표현식과 문이 서로 뒤섞여, 어떤 하위 프로그램이든 임의의 부수효과를 일으킬 수 있게 됩니다. 반면 하스켈은 IO 타입을 통해 타입 수준에서 문이라는 개념을 명시적으로 도입함으로써 이와 반대의 입장을 취한 듯합니다(엄밀히는 타입 생성자지만 설명의 편의를 위해 타입이라 하겠습니다). 이렇게 함으로써 문과 표현식의 암묵적 뒤섞임은 비표준이 되고, 문과 표현식의 명시적 구획은 타입 시스템에 의해 강제될 수 있게 됩니다.

그렇다고 해서 양자가 전혀 섞일 수 없다는 뜻은 아닙니다. 표현식이 여전히 IO a 타입을 반환할 수 있고, IO 동작이 표현식의 평가를 유발할 수도 있습니다. 다만 표준적이고 관용적인 하스켈에서는 이러한 혼합의 범위가 매우 엄격히 통제되고 의도적으로 제한됩니다. 물론, 무엇을 하고 있는지 아는 사람들을 위한 탈출구도 존재하긴 합니다.

종합하면, 하스켈의 IO aString, Either e a, Int 같은 값으로 보기보다는, 런타임에 실행될 때(현재의 세계 상태가 주어졌을 때) 부수효과를 수반할 수도 있는 어떤 행동/문을 나타내거나 그에 대한 서술로 보는 편이 더 정확할 것입니다. 실행 결과로는 타입 a의 값을 산출합니다. 이런 관점은 내부적으로 IO가 상태 함수(s -> (a, s))로 정의되어 있다는 사실과도 어느 정도 맞닿아 있습니다.

#haskell#software-engineering