이 글은 내가 예전에 쓴 하스켈 do, 스칼라 for 이해하기 (feat. 모나드)의 후속편임. 안 봤으면 그 글부터 보고 오길 바람.
모나드 모르면 위에 글부터 봐야 이 글도 이해가 될것임.
제목 보면 알겠지만 이 글은 주제가 좀 특이하다.
나같은 듣보잡 뉴비도 제대로 된 모나드 강의 잘만 만드는데 왜 지금까지 다른 자들은 나처럼 하지 못하고 20년이 넘게 꼬라박았느냐가 그것이다.
왜 이런 연구를 했냐면 본문에도 이유가 나오지만 사실 그것보다 그냥 내가 궁금해서임.
위에 글 보면 '드디어 하스켈 정상인 등판' 이러면서 그동안 하스퍼거들 개소리 웅얼대는거 좃같았다는 코멘트가 있는데 표현은 격해도 맞는 말이다.
정상인이라면 이 정도 개념을 설명 못해서 웅얼대서는 안 된다.
충격적인 사실은 그 정상인이 거의 없다는 거다.
모나드 사람답게 설명하는게 '하스켈 정상인' 의 조건이면 난 아직 나 말고 다른 하스켈 정상인을 보지 못했음. 아무도 못 만드니까 답답해서 내가 만든거.
문제는 내가 다른 모나드 강의들을 한두개 본것도 아니고 나름 모나드 튜토리얼 모음 사이트에 있는 것들은 한번씩 다 읽어봤는데도 그렇다는 거다.
내게는 '얘네들 대체 뭐함?' 이라는 의문이 생겨났으며 그래서 한 게 바로 이 연구라고 할 수 있겠다.
이 칼럼은 예고한 github의 내 모나드 강의 프로젝트에도 거의 그대로 들어갈 것임.
들어가는 정도가 아니라 어쩌면 내 강의 전체에서 제일 중요한 글이 되지 않을까 싶다. 의외로 아무도 안 다룬 주제기도 하고 결정적으로 재밌음.
https://github.com/attltb/Lecture-Monad
이곳이며 아직 준비중이지만 기대되면 미리미리 별 눌러주길 바람.
# 2022-09-05
일부 내용에 오류가 있어서 수정함. 본문은 수정하지 않았고 수정본 링크로 남겨둠. 여기로 가면 된다.
새로 읽는 사람들은 아래쪽 글 읽지 말고 위의 링크로 가는 것을 추천.
------------------------------------------------------------------------------------------------------------------------------------------------------------------------
0. 서문
왜 이렇게 오래 걸렸을까?
모나드는 거의 20년 동안 함수형 프로그래밍의 뜨거운 이슈 중 하나였다. 그동안 만들어진 모나드 강의 및 튜토리얼들은 셀 수도 없을 만큼 많다. 모나드가 쉽게 설명될 수 있는 것이었다면 왜 이전의 모나드 튜토리얼들은 다 실패했을까?
이러한 질문은 지적 호기심의 측면에서만 중요한 것이 아니다. 나쁜 설명은 많은 경우 대상에 대한 결함 있는 이해로부터 비롯된다. 그러한 실패가 오랫동안 집단적으로 발생하였다는 사실은 대상에 대해 일반적으로 받아들여진 견해 자체에 오류가 있었음을 의미할 수 있다. 여기에 우리가 이 문제에 지대한 관심을 기울여야 하는 이유가 있는 것이다.
게다가 작동하는 모나드 강의를 만드는 것은 작금의 모나드 튜토리얼 비극을 끝내기 위해 우리가 해야 하는 두 가지 일 중 하나일 뿐이다. 다른 한 가지는 자꾸 재생산되는 나쁜 설명들을 뿌리뽑는 것이다. 단순히 그것들이 나쁘다고 말하는 것만으로는 불충분하다. 나쁜 설명들은 바탕이 되는 오류들을 제거하지 않으면 언제든 다시 되살아나고 만다. 과거의 실패에 대한 철저한 원인 분석과 반성, 그리고 그 반성에 바탕을 둔 비판만이 그들을 없앨 수 있다.
이 글에서 우리는 역사를 거슬러 올라가 모나드가 처음에 어떻게 프로그래머들에게 이해되었고 그것이 어떻게 변화해 왔는지 볼 것이다. 독자들은 모나드를 프로그래머들에게 처음 소개한 이들이 어떤 실수를 범했고, 왜 그 실수들이 수정되지 못하고 더욱 큰 오류를 야기했으며, 그 결과 지금의 모나드 튜토리얼 비극을 발생시켰는지 알게 될 것이다.
이 모든 이야기의 교훈과 앞으로 같은 실패가 다시 발생하지 않게 할 수 있는 진정한 해결책은 마지막에 다루어진다.
1. 발단 - 재앙의 씨앗
1-1. 실패의 역사적 뿌리
모나드는 Engenio Moggi에 의해 1988년[1]에 처음으로 컴퓨터 과학 분야에 소개됐다.
그는 A 타입의 값을 반환하는 순수하지 않은 함수의 상당수를 어떤 타입 컨스트럭터 T에 대해 T<A>타입의 값을 반환하는 순수 함수로 대신 나타낼 수 있다는 데 주목했다.
T 가 모나드일 경우 모나드의 성질에 의해 A에서 T<B>로 가는 함수와 B에서 T<C>로 가는 함수의 합성도 항상 자연스럽게 정의할 수 있었다. 비 순수 함수와 그들 간의 합성을 모두 순수 함수만 가지고 나타낼 수 있었던 것이다. 이것을 이용하면 람다 대수의 틀 내에서 순수하지 않은 프로그램을 연구할 수 있다는 것이 Moggi의 아이디어였다.
물론 이 설명은 Moggi의 발견을 프로그래머들이 이해하기 쉽게 내가 다시 재구성한 것이다. Moggi가 스스로 그의 발견을 표현한 방식은 아주 달랐다. 1990년대 초에 실제로 일어난 일을 이해하려면 우리는 그의 표현을 볼 필요가 있다.
앞서 말한 실패할 수 있거나, 부수 작용이 있거나, 기타 여러 이유로 순수하지 못한 함수들을 그는 계산(computation)이라고 불렀다. 계산들을 순수 함수로 나타낼 수 있게 해주는 T들은 계산개념(notion of computation)이라고 불렸다.
계산개념에 대한 그의 설명은 이후 20년이 넘게 지속된 실패를 야기한 오류의 직접적인 원인이 되었기 때문에 전체를 읽어볼 만한 가치가 있다:
The basic idea behind the categorical semantics below is that, in order to interpret a programming language in a category C, we distinguish the object A of values (of type A) from the object TA of computations (of type A), and take as denotations of programs (of type A) the elements of TA. In particular, we identify the type A with the object of values (of type A) and obtain the object of computations (of type A) by applying an unary type-constructor T to A. We call T a notion of computation, since it abstracts away from the type of values computations may produce.[2] (아래의 범주론적 의미 체계의 바탕이 되는 아이디어는 이렇다. 카테고리 C안에서 프로그래밍 언어를 해석하기 위해 우리는 (A 타입의) 값들의 대상 A와 (A 타입의) 계산들의 대상 TA를 구분했고, (A 타입의) 프로그램을 TA의 원소로 나타냈다. 특히 우리는 (A 타입의) 값의 대상을 타입 A로 정하고 단항 타입 컨스트럭터 T를 A에 적용하는 것으로 (A 타입의) 계산의 대상을 얻었다. 우리는 T를 계산개념이라고 부른다. 그것은 계산이 생성할 수 있는 값으로부터 추상화된 것이기 때문이다.)
독자들 중에는 혹시 Moggi의 논문 다른 곳에 더 엄밀한 정의가 있고 이건 그냥 그것의 짧은 요약이 아닐까 하고 생각하는 이들도 있을 것이다. 아니다. 정말로 이 혼란스러운 몇 문장이 그가 그의 논문의 핵심 용어, 계산개념을 정의하기 위해 쓴 것의 전부였다.
문제는 거의 무성의하다고 해도 좋을 만큼 간략하게 정의된 이 개념 안에 치명적인 오류를 야기할 수 있는 위험이 숨어 있었다는 것이다.
1-2. 이상한 환상
Moggi의 이른바 계산개념들이 이름과 달리 꼭 계산의 개념을 포함할 필요가 없다는 것은 명백하다. A를 반환하는 비결정적인 함수는 Set<A>로 가는 순수 함수로 대신 나타낼 수 있지만 그게 Set에 무슨 비결정성의 개념이 들어 있어서는 아니다. Optional 등의 다른 계산개념들도 마찬가지다. 소위 계산개념이란 단지 우발적으로 T<A>타입의 값을 반환하는 함수들이 어떤 계산으로 해석될 수 있는 T들에 지나지 않는다.
그러나 이 점은 1990년대 초에 Moggi의 논문을 통해 프로그래밍의 모나드를 처음 접한 사람들에게는 그렇게 명백하지 않았다. Moggi 본인부터 계산개념을 그렇게 설명하지 않는다. 이 문장을 보라.
... we identify the type A with the object of values (of type A) and obtain the object of computations (of type A) by applying an unary type-constructor T to A. (특히 우리는 (A 타입의) 값의 대상을 타입 A로 정하고 단항 타입 컨스트럭터 T를 A에 적용하는 것으로 (A 타입의) 계산의 대상을 얻었다.)
카테고리 이론을 모르는 독자들을 위해 쉽게 풀어 설명하자면, 그는 여기서 이것을 말하고 있다: T<A>는 A타입의 값을 생성하는 계산의 타입이다!
Moggi의 설명은 잘 납득이 가지 않는다. 계산을 나타내는 것은 Optional<A>나 Set<A>타입을 반환하는 함수들이다. 어떻게 Optional<A>와 Set<A> 자체가 계산의 타입이 될 수 있다는 걸까?
간단하다. Moggi가 그의 논문에서 다루는 계산들은 값이 아니다. 람다 대수로 씌여진 표현식이다. 그것의 타입이 T<A>라는 건 단지 그것이 T<A>타입을 반환하는 표현식이라는 것을 의미한다. 이런 표현식들은 T<A>를 반환하는 0인수 함수나 마찬가지므로 그것이 계산을 나타낼 수 있다는 건 놀랄 일이 아니다. 가령 Optional<A>를 반환하는 표현식이 실패할 수 있는 계산을 나타낼 수 있음은 당연하다.
그러나 Moggi는 이런 배경을 전혀 말해 주지 않는다. 문제는 그의 발견을 처음으로 프로그래머들에게 알린 초기 모나드 전도사들조차 Moggi의 것보다 오히려 더 이상한 설명으로 오류를 부채질했다는 것이다. Philip Wadler의 이 설명을 보라.
Just as the type Value represents a value, the type M Value can be thought of as representing a computation.[4] (Value타입이 값을 나타내듯이, M Value타입은 계산을 나타내는 것으로 생각될 수 있다.)
뭐라고 해야 할까? 내가 보기엔 이 설명은 그냥 틀렸다. 명백히 대부분의 모나드 M에 대해 M Value타입은 계산을 나타내지 않는다. 그러한 타입을 반환하는 표현식이 계산을 나타낼 수 있는 것은 사실이다. 하지만 그렇다고 그 타입이 계산을 나타낸다고 할 수는 없다. 프로그래머에게 타입의 의미는 언제나 그 타입의 값에 관한 것이다.
Wadler가 무엇을 생각하고 위와 같은 설명을 한 것인지는 불분명하다. 문제는 이런 설명들이 그것이 정확히 무엇을 의미하는지에 대한 최소한의 합의조차 없이 함수형 프로그래밍 커뮤니티 전체로 퍼져 나갔다는 것이다.
많은 사람들은 그 설명들을 이런 의미로 받아들였다: T<A>는 어떤 계산과 밀접하게 관계된, 그것의 본질적인 부분을 나타낸다.
물론 이것은 사실이라 쳐도 초기 모나드 전도사들의 설명을 기껏해야 환유의 관점에서 정당화할 수 있을 뿐이다. 그런 것이 설명으로 취급될 수 있다면 다른 모호한 은유라고 그러지 못할 이유는 없다. 훗날 수 많은 얼치기 모나드 전도사들이 모나드를 설명한답시고 자기 나름의 모호한 은유를 만들어낸 것도 당연했다.
게다가 그것은 사실도 아니다. 상술했듯이 모나드는 그 모나드 타입을 반환하는 함수나 표현식이 계산을 나타낼 수 있기에 계산개념이라고 불릴 뿐이다. Moggi 자신이 쓴 이 문장을 보라.
We call T a notion of computation, since it abstracts away from the type of values computations may produce. (우리는 T를 계산개념이라고 부른다. 그것은 계산이 생성할 수 있는 값으로부터 추상화된 것이기 때문이다.)
대부분의 T<A>의 의미가 어떤 식으로든 계산과 관계가 있는 것은 사실이다. T<A>를 반환하는 함수나 표현식이 계산이 되려면 당연히 T<A>도 그것과 관계가 있기는 해야 한다. 그러나 그 관계가 무엇인지는 모나드마다 다르다. 일관된 의미에서 T<A>가 계산의 무엇을 나타낸다고 하는 것은 불가능하다.
아무튼 당시에는 아무도 이런 것을 설명해 주지 않았다. T<A>가 계산을 나타낸다는 모호하고 혼란스러운 설명만 아무런 부연 설명 없이 끝없이 되풀이되며 퍼져 나갔다. 그 결과 이상한 환상이 만들어졌다. 각 T가 실제로 계산의 개념으로써 어떤 계산의 본질적인 무언가를 나타낸다는 것이다. 계산개념은 그 모든 계산의 본질들을 묶어 주는 아주 추상적인 무언가라고 믿어졌다.
1-3. 모나드와 계산개념
Moggi가 결코 모나드 전부를 계산개념이라고 부르지 않았다는 사실은 주목할 만하다. Moggi에게 그것은 모나드의 부분집합에 불과했다. 그는 모든 모나드가 아니라, 모나드 중 T<A>가 계산을 나타낼 수 있는 T들만 계산개념이라고 불렀다.
모나드를 처음 프로그래밍 언어에 도입한 이들이 충분히 사려 깊고 조심스러웠다면 어떻게 했을까? 그들은 이 두 개념의 분리를 유지시켰을 것이다. 그들은 모나드를 이런 식으로 설명했을 것이다.
- 모나드는 타입 컨스트럭터 T중 임의의 A, B에 대해 A->B 타입의 함수를 T<A>->T<B>타입으로 변환할 수 있으며, A에서 T<A>로의 형 변환과 T<T<A>>에서 T<A>로의 형 변환을 모두 갖는 것을 뜻한다.
- 그것이 유용한 이유는 T가 모나드일 경우 모나드의 성질에 의해 A에서 T<B>로 가는 함수와 B에서 T<C>로 가는 함수의 합성을 자연스럽게 정의할 수 있기 때문이다. A에서 T<B>로 가는 함수는 A에서 B로 가는 함수의 어떤 논리적 확장을 나타낼 수 있다. 가령 그것은 A에서 B로 가는 특정한 비 순수 함수, 즉 계산을 나타낼 수 있으며 이는 매우 유용하다.
물론 이 설명은 내가 모나드에 대한 작동하는 설명 에서 시도한 설명과 정확히 같다. 이 정의와 설명이 채택되었다면 모나드 튜토리얼들의 실패가 지금처럼 20년이 넘게 이어지지는 않았으리라. T<A>를 반환하는 함수가 계산이 되는 T들을 따로 뭐라고 부르든 거기서 무슨 오류가 발생하든 마찬가지다. 피해는 발생했겠지만 그 피해가 지금처럼 길게 이어지지는 않았을 것이다.
곧 보게 되겠지만 모나드를 처음 프로그래밍 언어에 도입한 이들은 정확히 이것에 반대되는 선택을 했다. 그들은 모나드에서 그들이 불필요하다고 판단한 다른 의미와 용도를 빼고 그들이 중요하다고 판단한, 그러나 당시 그들을 포함한 그 누구도 정확히 설명할 방법을 몰랐던 그 계산개념으로써의 의미와 용도만 남기려 했다. 선구자들이 계산개념 속에 이상한 환상의 형태로 남겨 놓은 작은 재앙의 씨앗을 완전한 재앙으로 만든 것은 바로 그 선택이었다.
2. 전개 - 끔찍한 선택
2-1. 모나드의 '개선'?
모나드를 실제로 프로그래밍 언어에서 사용할 수 있는 유용한 툴로 처음 소개한 건 Philip Wadler로 여겨진다.
1990년의 논문에서 Wadler는 모나드를 비교적 원본에 가까운 형태로 소개한다. 모나드는 A->B 타입의 함수를 T<A>->T<B>타입으로 바꾸는 lift, A타입을 T<A>타입으로 바꾸는 unit, 그리고 T<T<A>>타입을 T<A>타입으로 바꾸는 join함수를 가진, 그리고 이들이 특정 조건을 만족시키는 타입 컨스트럭터였다.[3]
그러나 그로부터 2년 후 같은 저자는 모나드를 전혀 다르게 소개한다. 모나드는 A->T<A>타입의 함수 unit과 T<A>->(A->T<B>)->T<B> 타입의 함수 bind를 가진, 그리고 이들이 특정 조건을 만족시키는 타입 컨스트럭터다.
For our purposes, a monad is a triple (M, unitM, bindM) consisting of a type constructor M and a pair of polymorphic functions.[4] (우리의 목적상, 모나드는 타입 컨스트럭터 M과 두 다형 함수들로 이루어진 삼중쌍 (M, unitM, bindM)이다.)
물론 이 정의는 아무런 근거도 없이 갑자기 튀어나온 것은 아니다. 그것은 모나드와 수학적으로 동치인 Kleisli triple이라는 것의 정의를 약간 변형한 것이다.
1991년에 Moggi의 모나드에 대한 두 번째 논문[2]이 출판된 이래 계산개념은 주로 이 Kleisli triple을 통해 설명되었다. 그것의 정의가 모나드의 원본 정의보다 계산개념의 계산을 나타내는 용도와 훨씬 더 깊은 관련을 가지고 있기 때문이었다.
Wadler의 새로운 모나드 정의도 마찬가지였다. lift와 join은 계산과 관련된 특별한 용도가 없는 반면, bind는 A를 반환하는 계산 (상술했듯이 이들은 T<A>를 반환하는 함수로 구현된다) 과 다른 계산을 합성할 때 직접적으로 이용될 수 있었다. Wadler의 논문은 주로 모나드로 계산을 나타내는 방법에 관한 것이었으므로 계산과 직접적인 관련이 있는 정의를 사용하는 것이 더 편한 건 당연했다.
물론 이 모든 것은 1992년의 그 논문의 맥락에서 그렇다는 것이다. 프로그래밍의 모나드를 정의하는 방법으로써 무엇이 더 나은가는 완전히 다른 문제다.
그럼에도 많은 사람들은 Wadler가 편의를 위해 변형한 모나드의 정의를 마치 프로그래밍의 모나드에 대한 더 나은 정의인 것처럼 받아들였다. 아무튼 모나드는 실제로 프로그래밍에서 주로 계산을 나타낼 때 사용되었고, bind는 실제로 join과 lift보다 유용했다.
결국 4년 후 하스켈은 bind가 >>=로 바뀐 것을 빼면 정확히 Wadler의 두 번째 모나드 논문에서 제안된 것과 같은 형태로 모나드 타입클래스를 도입한다. 나중에 모나드를 도입한 다른 많은 프로그래밍 언어들도 하스켈의 선택을 따랐다.
그러나 그것은 그들이 기대했던 것처럼 프로그래밍의 모나드를 더 직관적이고 이해하기 쉽게 만들어 주지는 않았다. 모나드는 훨씬 더 당황스럽고 이해하기 힘든 개념이 되었다. 이후 20년이 넘게 이어진 모나드 튜토리얼의 일관된 실패는 바로 1996년의 그 선택의 결과였다.
2-2. 실패의 이유들
무엇이 문제였을까? 그들은 단지 모나드에서 잘 사용되지 않는 기본 함수 두 개를 빼고 더 자주 사용되는 함수를 대신 넣었을 뿐이다. 당연히 이것은 모나드를 더 직관적이고 이해하기 쉽게 만들어주지 않을까?
아니다. 그 생각이 얼마나 얕고 어리석은 것인지 보이는 데는 특별히 고차원적인 논리도 필요치 않다. 가장 피상적인 관찰조차 그것의 오류를 극명하게 보여준다.
우선 >>=는 알려진 모든 모나드에서 lift와 join보다 훨씬 더 복잡하다. 이론적으로는 >>=가 더 간단한 함수고 lift와 join이 >>=로부터 정의되는 더 복잡한 함수인 경우도 생각할 수 있는 것은 사실이다. 그러나 그런 게 정말로 있을 수 있는지는 의심스럽다.
lift와 join이 기본 함수라면 프로그래머들이 개별 모나드의 >>=를 직접 공부할 필요는 없다. >>=는 항상 두 함수로부터 같은 형태로 정의 가능하기 때문이다. >>=는 그냥 lift와 join에 기반한 고차 함수 중 하나가 되었을 것이다. 개별 모나드들은 훨씬 더 간단한 lift와 join을 공부하는 것만으로도 충분히 배울 수 있었을 것이다.
이 차이는 크다. 나는 이미 모나드에 대한 작동하는 설명에서 Optional 모나드와 List 모나드를 코드 한 줄도 없이 쉽게 설명한 바 있다. 그것은 내가 이 모나드들을 lift와 join을 통해 설명했기 때문에 가능했다. >>=도 그런 식으로 설명할 수 있을까? Optional의 것은 아마 가능할 것이다. List의 것은 어떨까? 억지로 하면 불가능하지는 않겠지만 그 설명을 독자들이 쉽게 이해할 수 있을지는 별개의 문제다.
State나 Cont등의 비교적 난해한 모나드에서 차이는 더욱 커진다. 이들의 >>=연산자를 코드만 보고 바로 이해하는 건 하스켈 초보자들에게는 결코 쉬운 일이 아니다. lift와 join는 논리적으로나 실제 구현으로나 압도적으로 더 간단하다.
그러나 IO 모나드만큼 두 정의의 차이를 극명하게 보여주는 모나드는 없을 것이다. 나는 모나드에 대한 작동하는 설명에서 그것을 코드 한 줄 없이 설명해낸 바 있다. 그 설명에는 단 하나의 단점만 있을 뿐이다. 너무나 짧고 쉬워서 독자들로 하여금 IO 모나드가 본래 얼마나 어려운 주제였는지를 망각하게 한다는 것이다.
독자들은 지난 20년 동안 전체 하스켈 커뮤니티가 그 IO 모나드 하나를 설명하지 못해서 가능한 모든 수단을 동원했다는 사실을 아는가? 그리고 우리는 그것 모두가 완전하게, 가능한 한 가장 비참하게 실패하는 꼴을 보아 왔다. 그들의 기이할 정도로 일관된 실패는 물론 정의의 차이만으로는 다 설명되지 않는다. 그러나 비효율적인 정의에 대한 집착이 그들이 실패해 온 이유에서 큰 비중을 차지하고 있다는 것은 확실하다.
가장 피상적인 관점에서 보더라도 하스켈이 모나드에서 join과 lift의 기본 함수 지위를 박탈하면서 한 일은 >>=를 항상 간단한 두 함수로 쪼갤 수 있다는, 모나드에 본원적으로 존재했던 구조 하나를 거세시킨 역-리팩토링에 지나지 않는다. 대부분의 독자들은 굳이 다른 문제점까지 들어 보지 않아도 이것 하나만으로 그 선택이 얼마나 잘못된 것이었는지 능히 짐작할 수 있을 것이다.
join과 lift를 버린 하스켈의 모나드 구현은 그 외에도 많은 문제를 야기했다. 그것은 카테고리 이론에서 유래한 또 하나의 유용한 개념 도구, Functor와 모나드의 연결도 약화시켰다. 결과적으로 모나드를 배우고자 하는 이들은 모나드를 Functor라는 중간 과정 없이 처음부터 이해해야 했으며, 이는 그들이 모나드를 배우는 것을 더 어렵게 했을 뿐 아니라 연관된 두 개념을 마치 별개의 개념처럼 배우게 함으로써 하스켈 자체의 학습 효율도 떨어뜨렸다.
하스켈의 모나드 구현은 프로그래머가 무엇이 모나드고 무엇이 모나드가 아닌지 스스로 구분하는 것도 힘들게 했다. lift와 unit, join으로 이루어진 정의는 개념화하기 쉽다. 왜 Optional이 모나드일까? 임의의 함수에 대해 그걸 바탕으로 하는 Optional사이의 함수가 항상 존재하며, 값은 값을 가진 Optional로 볼 수 있고, Optional의 Optional 또한 Optional로 볼 수 있기 때문이다. 어떤 타입 컨스트럭터에 >>=를 정의할 수 있는지 판단하는 건 훨씬 어렵다. 그것은 일반 사용자들이 스스로 모나드를 찾는 것을 사실상 불가능하게 했고 그들의 모나드에 대한 이해는 물론 생산성까지 추락시켰다.
2-3. 불구가 된 모나드
그러나 진정으로 치명적인 문제는 따로 있었다. 하스켈의 모나드 구현은 모나드 전체를 Moggi가 발견한 그것의 특정 용도, 즉 계산을 나타내는 용도에 종속시켰다.
결과적으로 모나드는 훨씬 덜 매력적인 개념이 되었을 뿐 아니라, 더 취약해졌다. 이제 그것은 오직 그것을 정당화하는 단 하나의 용도가 완전히 이해되었을 때만, 그리고 그 용도가 모나드를 완전히 이해시키기에 충분할 경우에만 완전히 이해될 수 있었다. 둘 모두 사실이 아니었고, 이는 결국 모나드를 이해할 수 없는 개념으로 전락시켰다.
모나드라는 개념이 Moggi가 그것을 발견하기 훨씬 전부터 수학에 존재했던 데는 이유가 있다. 그 개념은 스스로를 정당화하기에 충분할 만큼의 자연스러움을 가지고 있었다. 모나드의 본래 정의를 채택하는 한 프로그래밍의 모나드도 마찬가지다. lift와 unit, join은 이해하기 쉽고 의미도 명백하다. 이들에 의존하는 정의를 이해하기 위해 굳이 계산개념 따위는 필요치 않다.
모나드의 새로운 정의는 이 모든 것을 바꿨다. 그것의 비직관적이고 비대칭적인 생김새는 그것을 처음 접하는 이들에게는 거의 임의적인 것처럼 보였다. 모나드는 더 이상 자연스러운 개념이 아니었고 정당화를 절실히 필요로 했다. 그리고 그것의 기이한 새로운 정의를 정당화할 방법은 그것의 알려진 유일한 용도, 계산개념으로써의 용도에 호소하는 것 말고는 없었다.
물론 이러한 상황은 어느 정도 하스켈 개발진들이 의도한 것이기도 했다. 그들에게는 모나드에서 Moggi가 발견한 용도와 무관한 것들은 모두 불필요해 보였다. 기존 정의는 모나드가 계산개념이 아니라 다른 것일 수도 있다는 인상을 준다는 바로 그 이유 때문에 폐기되어야만 했다. 새로운 정의는 그것이 오직 계산개념으로만 이해될 수 있다는 바로 그 이유로 인해 선택됐다.
그러나 그런 선택은 정말로 바람직한 것인가? 모나드의 모든 부분에서 오직 계산과 관계된 것만 남겨서 오직 계산개념만으로 모나드 전부를 설명할 수밖에 없도록 하는 것이 정말 모나드의 설명에 도움을 주기는 하는 것인가?
내 모나드에 대한 작동하는 설명을 보라. 그것은 우선 모나드를 본래 정의를 통해 소개한 후 그것의 계산을 나타내는 용도는 나중에 '모나드가 프로그래밍에서 유용한 이유는 무엇인가?' 라는 질문에 대한 대답으로써 따로 다룬다.
계산과 무관한 정의를 이용한 것이 모나드의 계산을 나타내는 용도를 설명하는 걸 방해했는가? 두 설명이 충돌하거나 혼란을 불러일으켰는가? 아니다. 둘은 오히려 서로를 보완했다. 모나드의 간단한 정의가 그것에 직관성을 부여하고 계산개념으로써의 용도가 그것에 필요성을 부여했기에 그것은 훨씬 더 견고한 개념이 될 수 있었다. 무엇이 문제인가?
1996년의 하스켈 개발자들의 선택을 옹호할 수 있는 논거는 하나뿐이다. 프로그래머가 굳이 모나드의 본래 정의를 알 필요는 없으며, 그러한 것을 공부하는 것이 일종의 지적 역량의 낭비에 해당한다는 것이다.
그러나 인간의 기억은 컴퓨터 메모리 같은 것과는 다르다. 우리가 어떤 개념을 얼마나 더 쉽게 기억할지는 기억할 내용이 많냐 적냐보다는 그것이 우리가 아는 다른 개념들과 얼마나 더 밀접하게 얽혀 있느냐에 달려 있다. 모나드의 단순하고 직관적인 정의와 그 정의로부터 모나드의 계산개념으로써의 기능을 유도할 수 있다는 사실은 우리가 계산개념을 이해하는 것을 방해하기는커녕 돕는다. 훨씬 더 쉬운 것으로부터의 연결고리가 그것을 지탱하기 때문이다.
모나드의 정의의 변경은 모나드 튜토리얼들에 실질적으로 아무 이익을 가져다주지 못했을 뿐 아니라 그 개념을 훨씬 덜 흥미롭게 함으로써 직접적인 해악을 끼쳤다.
많은 사람들이 모나드라는 개념에서 매력을 느낀 건 단지 그것이 계산을 나타낼 수 있다는 사실 때문만은 아니었다. 그러한 쓰임새가 계산이나 함수의 확장 따위와는 전혀 무관해 보이는 간단한 정의로부터 발생한다는 사실 때문이기도 했다. 새로운 모나드 개념에 그런 매력은 없었다. 그것은 이전보다 훨씬 덜 매력적인 것이 되었고 정확히 같은 정도로 덜 직관적인 것이 되었다.
2-4. 계산개념의 한계
애초에 모나드라는 개념을 오직 계산개념으로써의 용도만 가지고 완전히 설명하는 것은 가능하기는 한 것인가? 나는 회의적이다. 최소한 아래의 조건들이 갖춰져 있어야 그것을 위한 시도라도 해볼 수 있다.
- 그 용도는 단독으로 모나드를 완전히 이해시킬 수 있어야 한다. 그것은 모나드에 관한 모든 중요한 질문에 다 대답해줄 수 있어야 하며, 그것의 일부 측면만을 설명하거나 일부의 모나드만을 설명하는 것이어서는 안 된다.
- 그 용도는 모나드에 앞서 우선 명백히 이해되어야 한다. 모나드의 모든 것에 대한 설명이 그것에 의존하므로 그것의 이해가 불분명하면 모나드의 모든 것이 불분명해지고 만다. 그것은 반드시 명확한 말로 표현되어야 하며, 이미 모나드 사용 경험이 있는 이들만 공감 가능한 어설픈 환유나 횡설수설에 의존해서는 안 된다.
우리가 1장에서 확인했듯이, 1996년에는 명백히 둘 모두 사실이 아니었다.
임의의 모나드 T에 대해 모나드의 정의가 말해 주는 건 그것이 항상 A에서 T<A>로 가는 함수 unit을 가지고 있고, A에서 T<B>로 가는 함수와 B에서 T<C>로 가는 함수를 재료로 A에서 T<C>로 가는 함수를 만들 수 있게 해 주는 기능을 가지고 있다는 것이다. 모나드 법칙은 또한 이 기능으로 unit과 다른 함수 f를 재료로 만든 함수가 항상 f임을 알려 준다.
모나드의 새로운 정의를 정당화할 수 있는 설명이 있다면 이러한 T에 대한 일반적인 설명일 것이다. 그러한 설명은 가능할 수도 있지만 적어도 계산개념은 그것이 아니다. 그 속에는 모든 모나드에 대한 설명으로 오해될 경우 오직 해악만 끼치는 개념적인 군더더기가 너무나 많다.
문제는 계산개념이 모나드에 대한 유일한 설명 수단이 될 경우 그러한 오해가 필연적으로 만들어진다는 것이다. 얼마나 많은 사람들이 '모나드는 계산을 나타내는 것이다.' 따위의 말들을 하고 있는지 보라. 계산개념은 Moggi에 의해 처음 정의될 때부터 모나드의 한 부분집합이었다. 그것을 모든 모나드에 대한 설명으로 취급하는 것은 부정확하며 오직 혼란만 가중시킨다.
모나드가 계산을 나타내는 용도로 쓰일 수 있다는 사실은 왜 그 개념이 지금과 같은 형태로 존재해야 하는지도 전혀 설명해주지 못한다. 단순히 계산을 나타내기 위해 만들어진 도구라고 하기엔 모나드의 정의와 사용법은 너무나 부자연스럽기 때문이다.
왜 계산을 나타내기 위해 A->T<B>가 계산을 나타내는 타입 컨스트럭터를 사용하는가? 계산을 나타내는 수단으로써는 T<A, B>가 A에서 B로 가는 계산을 나타내는 2인수 타입 컨스트럭터가 명백히 더 자연스럽지 않은가?
왜 계산들의 합성 수단이 꼭 T<A>->(A->T<B>)->T<B> 타입의 함수로 주어져야 하는가? 계산의 합성 수단이 필요했다면 (A->T<B>)->(B->T<C>)->(A->T<C>) 타입의 더 직설적이고 일반적인 함수를 요구하는 정의를 쓰는 것이 명백히 더 자연스럽지 않은가?
모든 의문은 모나드가 계산개념 따위의 도움 없이도 충분히 정당화될 수 있을 만큼 자연스러운 별개의 정의를 가지고 있다는 사실, 그리고 그로 인해 그것이 필연적으로 보편적이며, 그 결과 생각할 수 있는 대부분의 계산을 실제로 어떤 모나드로 나타낼 수 있다는 사실을 인식해야 완전히 풀린다. 모나드의 진짜 정의를 숨긴 채 그것을 전달하는 것은 어렵다.
우리가 진정으로 어떤 개념을 이해하고 직관화하게 되는 것은 단순히 그것의 정의와 사용법을 알게 되었을 때가 아니다. 그것이 왜 필연적으로 그런 형태로 존재할 수밖에 없는지 설명할 수 있게 되었을 때다. 계산개념만 가지고 모나드에 대한 그런 설명을 할 수 있을까? 불가능하지는 않을지도 모른다. 그러나 훨씬 더 쉬운 방법을 놔 두고 굳이 그런 방법을 찾을 이유는 없다.
2-5. 은유로의 추락
물론 이 모든 것은 당시 모나드의 그 계산을 나타내는 용도라는 것이 무엇인지에 대한 명확한 이해가 존재하기라도 했을 경우의 얘기다. 실제 상황은 거의 비교도 할 수 없을 만큼 더 나빴다.
계산개념은 Wadler에 의해 프로그래머들에게 처음 소개되었을 때부터 이미 횡설수설이었다. Optional과 List를 계산의 타입이라고 부르는 그 말도 안 되는 소리들은 명백히 적절한 맥락 없이는 이해될 수 없었다. 아무도 그 맥락이 뭔지 정확히 알지 못했으므로 모나드 자체가 이해될 수 없는 것으로 전락하는 것은 시간문제였다.
후에 모나드 전도사들이 어떻게든 그 맥락을 만든답시고 한 시도들은 너무 나빠서 직접 보기 전에는 그 해악을 상상하기조차 어렵다. 아래는 가장 잘 알려진 모나드 튜토리얼 중 하나인 Jeff Newbern외 기타 저자들의 All about monads[5]에서 내가 발췌한 것이다.
... Perhaps one of the most helpful glosses of "monad" in Haskell's sense comes from Simon Peyton-Jones. He pointed out that in F#, they are called "workflows", a term he deemed very sensible... (... 어쩌면 하스켈의 "모나드" 에 대한 가장 도움이 되는 직관화 중 하나는 Simon Peyton-Jones의 것일지도 모른다. 그는 F# 에서는 모나드가 그가 아주 적합하다고 생각하는 단어인 "워크플로우" 로 불린다는 것을 지적했다...)
As much as anything, monads are strategies for solving coding problems that recur often, regardless of what you're writing. In this sense, the concept of the monad resembles what's been called "cross-cutting concerns" in software engineering. The List solves a common problem: you need a very basic collection of items of the same type, with some easy-to-understand behavior and performance characteristics. And then there's the Maybe type, which rescues you from having to write lots of null pointer checks -- or else debug code that doesn't have enough of them. And I/O makes it possible to interact with a program at all. (무엇보다, 모나드는 당신이 어떤 작업을 하든 간에 자주 나타나는 코딩 문제를 풀기 위한 전략이다. 이런 의미에서 모나드의 개념은 소프트웨어 공학에서 "횡단 관심사" 라고 불리는 것과 유사하다. List는 한 가지 일반적인 문제를 해결해 준다: 당신은 같은 타입을 가진 값들의 이해하기 쉬운 행동 및 성능상의 특징들을 가진 기본 묶음이 필요하다. Maybe는 아무리 많아도 부족한 널 포인터 체크 관련 디버그 코드를 일일히 작성할 필요로부터 당신을 구제해 준다. 그리고 I/O는 프로그램과 상호작용하는 것 자체를 가능하게 한다.)
More than this, however, monads help make strategies composable. The monad is a kind of meta-strategy for combining computations into more complex computations. Think of monads as a kind of type-disciplined approach to "pipelines" inside your program... (게다가 모나드는 전략들을 합성 가능하게 하는 것을 돕는다. 모나드는 계산을 더 복잡한 계산으로 합치는 일종의 메타-전략이다. 모나드를 당신의 프로그램 안의 "파이프라인" 에 접근하는 타입 제약적인 접근이라고 생각하라...)
In short, a monad is a way to structure computations in terms of values and sequences of computations using typed values. But since sequencing is often done by feeding one function into the next, you get some type discipline and computational leverage over what kinds of operations can follow previous operations. For those coming from languages where the semicolon is a statement separator in imperative control flow, the metaphor of "programmable semicolon" has helped many understand the advantages of monads. The monad determines how combined computations form a new computation and frees the programmer from having to code the combination manually each time it is required. Think of monads as "statically typed filters" (in the Unix sense of "pipes and filters") and you may be halfway there. (요컨대, 모나드는 타입이 있는 값을 이용해서 계산을 값과 계산들의 연쇄로 구조화하는 방법이다. 그러나 연쇄가 주로 한 함수를 다음 것에 전달하는 방식으로 이루어지므로, 앞의 연산 뒤에 어떤 연산이 이어질 수 있는지에 대한 몇몇 타입 규약과 계산상의 레버리지가 주어진다. 세미콜론이 제어 명령들 사이의 문장 구분자로 사용되는 언어를 공부하다 온 이들에게는 "프로그래밍 가능한 세미콜론" 의 비유가 모나드의 여러 유용성을 이해하는 데 도움을 주었다. 모나드는 결합된 계산이 어떻게 새로운 계산을 만들어 내는지를 결정하며 프로그래머들이 매번 필요할 때마다 결합을 위한 코드를 직접 작성하지 않을 수 있도록 해 준다. 모나드를 "정적 타입이 지정된 필터" (Unix의 "파이프와 필터"와 같은 의미에서) 라고 생각하라. 그러면 당신은 절반 정도 온 것이다.)
그래서 이게 다 무엇인가? 글쎄, 그냥 보이는 그대로 받아들이면 된다: 울부짖으며 승천하는 개소리. 이런 것도 글이라고 열심히 썼을 저자에게는 미안한 말이지만 나는 단지 조롱을 위해 이것을 길게 인용한 것이다.
위 글에서 명료한 것은 아무도 그것을 이해할 수 없다는 사실 뿐이다. 도대체 이 글에서 말하는 "전략" 이 뭔가? 저자는 그것이 자주 발생하는 코딩 문제를 해결한다는 것과 "횡단 관심사" 와 유사하다는 것 말고는 그것에 대한 아무 설명도 하지 않는다. 그는 대신 List와 Maybe와 I/O를 사례랍시고 보여 주지만 각각이 "전략" 이라고 주장되는 근거들은 너무나 달라서 그들 사이에서 의미 있는 공통점을 찾는 건 순수하게 논리적으로 불가능하다. 하물며 그런 "전략" 을 합성한다는 건 또 무슨 소리인가? 전혀 어디서도 정확한 정의가 설명된 적이 없는 "계산" 은 왜 또 갑자기 튀어나와서 가장 중요한 것이 되는가?
이 글에서 언급하고 있는 많은 것들이 실제로 모나드와 관계가 있는 것은 사실이다. 그러나 단순히 모나드와 관계가 있는 걸 다 끌어다 붙인다고 해서 그게 모나드에 대한 설명이 되지는 않는다. 어떤 개념이 이해가 되려면 우선 그 개념이 어떤 범주에 속하는 것인지부터 이해가 되어야 한다. 모나드가 "워크플로우" 였다가 "전략" 이었다가 "메타-전략" 이었다가 "계산의 구조화 방법" 이 되는 위 설명을 모나드를 처음 접하는 이들이 이해할 방법은 없다.[6]
저자에게는 왜 이런 혼란스럽고 명백히 누구에게도 아무 도움도 되지 않는 횡설수설이 필요했을까? 다음과 같은 말을 하기 위해서다.
Let's look at Maybe... which represents the type of computations which may fail to return a result. (Maybe를 보자... 이것은 결과를 반환하는 데 실패할 수 있는 계산의 타입을 나타낸다.)
훗날 모나드가 계산을 나타낸다는 말은 모나드에 대한 은유 중 하나로 받아들여졌다. 아무도 정확한 정의를 말하지 않는 계산이라는 말 자체의 모호한 의미, 어떻게 해석해야 할지 불분명한 위와 같은 혼란스러운 말들이 그것을 은유 외의 다른 어떤 것으로도 생각할 수 없게 했다.
잘 알려져 있지 않은 사실은 그 은유의 뿌리가 된 계산개념이라는 용어가 Moggi에 의해 처음 정의될 때만 해도 비교적 명확한 의미를 가지고 있었다는 것이다.
계산은 어떤 비 순수성을 가진 유사 함수를 의미했다. 계산개념은 T<A>를 반환하는 함수나 표현식이 A를 반환하는 어떤 계산을 대신 나타내는 데 쓰일 수 있는 T들을 의미했다. 이러한 개념들은 완벽하게 명확하다고는 할 수 없지만 적어도 은유는 아니다. 단지 의미론을 가지고 있다는 이유로 그들이 은유라면 'int타입은 정수를 나타낸다.' 같은 말들도 다 은유가 된다.
불행히도 Moggi는 그것들의 의미를 그의 논문에서 그리 충실하게 소개하지는 않았다. 그 결과 그것들은 프로그래머들에게 소개되면서 이상한 환상을 만들어냈다.
후대의 모나드 전도사들은 선구자들이 만들어낸 환상을 결코 극복해내지 못했다. 그들은 어떻게든 그 환상을 더 그럴듯해 보이게 만들기 위해 그들이 아는 것 중 그 환상과 어렴풋이라도 관계가 있어 보이는 모든 것들을 그 환상 위에 덕지덕지 붙였다. 계산이라는 은유는 그렇게 만들어졌다. 모나드는 그렇게 이해될 수 없는 개념이 되었다.
2-6. 계산의 밖
계산개념은 설령 오류 없이 완전히 이해되었다 해도 오늘날 모나드에 대한 설명에서 중요한 역할을 하기는 힘들었을 것이다. 프로그래밍에 쓰이는 유용한 모나드 중에는 Moggi가 정의한 계산개념의 범주 밖에 있는 것들도 많기 때문이다.
애초에 모나드가 계산을 나타내는 데 쓰일 수 있었던 건 단지 T가 모나드일 경우 A에서 T<B>로 가는 함수와 B에서 T<C>로 가는 함수의 합성을 자연스럽게 정의할 수 있기 때문이다. Moggi는 이런 함수들이 비 순수 함수들, 이른바 계산이 되는 경우에 주목했지만 그것이 다른 종류의 확장된 함수를 나타내지 말라는 법은 어디에도 없었다.
계산과 무관한 모나드의 활용은 일반 하스켈 사용자들의 코드에서도 쉽게 찾을 수 있다. 아래의 코드를 보라.
리스트 모나드가 계산개념으로써 비결정적인 계산을 나타낸다는 공식적인 설명을 가지고 있는 것은 사실이다. 이 해석을 따르고자 하는 이들은 위 코드를 이렇게 해석할 것이다: 2도 3도 5도 7도 될 수 있는 비결정적인 값을 비결정적인 계산을 수행하는 함수 plusminus에 넣어 비결정적인 결과값을 얻었다. 그러나 이 해석은 정말로 타당한 것인가?
현장에서 List모나드를 활용하는 누구든 찾아가서 물어 보라. 이건 그냥 조건에 맞는 값 여러 개를 반환하는 함수에 여러 값을 보내서 여러 결과값을 얻는 코드라고 할 것이다. 비결정성 어쩌고는 나쁜 이론가의 상상 속에만 존재하는 군더더기일 뿐이다.
위 예제에서 실제로 합성되고 있는 것은 '조건에 맞는 값 여러 개를 반환하는 함수' 다. 문제는 그것이 Moggi의 계산, 즉 어떠한 비 순수성을 가진 함수의 범주에 속하지 않는 전혀 다른 종류의 확장된 함수라는 것이다.
이론이 아닌 경험을 통해 모나드를 배운 후발주자들은 대부분 모나드를 계산이나 비 순수성과 연결짓지 않았다. 그들 중 일부는 계산개념에 기반한 공식적인 설명과 완전히 다른 자신만의 모나드 설명을 만들어냈다.
물론 후발주자들의 이해는 많은 경우 부정확하거나 불완전했고, 때로는 심각한 오류까지 담고 있었다. 그들의 설명 대부분은 이론가들의 것보다 더 나빴다. 이론가들은 흔히 이들의 한심한 모나드 설명을 비난했다. 그러나 애초에 이론가들 자신의 설명이 현실과 맞지 않았기 때문에 후발주자들이 다른 설명을 만들어낼 수밖에 없었다는 사실은 거의 인식되지 못했다.
여하튼 모나드를 계산이나 비 순수성하고만 연결짓는 설명은 이미 시대에 뒤쳐진 것이다. 그것은 모든 모나드는 고사하고 모든 유용한 모나드조차 설명하지 못한다. 아직도 많은 모나드 전도사들이 그러한 설명이 작동할 거라고 믿는다는 것은 놀라운 일이다.
2-7. 모나드는 무엇을 나타내는가?
그러나 계산조차 아니라면 무엇을 가지고 (유용한) 모나드를 설명해야 하는가?
프로그래밍의 (유용한) 모나드는 계산개념이었을 때도 충분히 경계가 모호하고 불분명했다. 이제는 그 설명조차 불충분했다. 그 거대한, 무한히 꾸물거리며 퍼져나가는 부정형의 괴물을 정제된 언어의 틀 안에 가두는 건 불가능해 보였다.
방법이 없지는 않았다. 다음 설명은 명확하고 완비적이며 모나드에 대한 설명 치고는 놀라울 만큼 이해하기도 쉽다.
모나드는 A에서 T<B>로 가는 함수가 A에서 B로 가는 함수를 기능적/의미론적으로 확장한 것으로 해석될 수 있는 모든 타입 컨스트럭터 T이다.
그러나 어쩌면 프로그래밍의 모나드에 대한 유일하게 정확한 설명일지도 모르는 이 설명은 영원히 발견되지 않았다. 단지 우발적으로 그것을 반환하는 함수가 계산으로 해석될 수 있었을 뿐인 T들 속에 실제로 계산의 개념이 있다고 믿은 선구자들처럼, 이후의 모나드 전도사들도 단지 우발적으로 그것을 반환하는 함수가 확장된 함수로 해석될 수 있었을 뿐인 타입 컨스트럭터들 속에 그들이 잘 모르는 어떤 공통적인 의미가 있을 거라고 믿고 그것을 찾으려고 애썼다.
프로그래밍의 모나드에 관한 영문 위키피디아 페이지[7]는 아마 정상적인 사람들의 눈에는 거의 확실히 횡설수설처럼 보일 C. A. McCann의 이 설명을 인용하고 있다.
For a monad 'm', a value of type 'm a' represents having access to a value of type 'a' within the context of the monad.[8] (모나드 'm'에 대해 'm a' 타입의 값이란 그 모나드의 맥락에서 'a' 타입의 값에 접근할 수 있는 것들을 나타낸다.)
물론 맥락이 어쩌고 하는 모나드 설명은 McCann이 2010년에 위 글을 작성하기 훨씬 전부터 존재했다. 그것은 서로 다른 수 많은 모나드 안에서 무언가 공통적인 의미를 찾으려고 한 모나드 전도사들의 가망 없는 노력의 결과 중 하나였다. 그것은 오직 맥락이라는 말 자체가 워낙에 모호해서 아무 모나드에나 다 끼워맞출 수 있었기 때문에 작동했다. 물론 맥락이라는 말 자체가 워낙에 모호해서 모나드가 아닌 것들도 다 끼워맞출 수 있었기 때문에 그것은 애초에 모나드를 모르는 사람에게 모나드를 설명하기에는 완전히 무용지물이었다.
나는 여기서 모나드 전도사들이 모나드를 한 마디로 설명한답시고 만들어낸 모든 헛소리들을 다 다루지는 않을 것이다. 명확한 말로 모나드를 설명하는 것이 불가능했기 때문에 그 설명들은 의도적으로 최대한 애매하고 불분명하게 만들어졌다. 모나드를 이미 이해한 사람들은 그 말들이 전달하고자 하는 것을 느낌으로 어느 정도 알아들을 수 있었다. 그러나 그렇지 않은 사람들에게 그것들은 철저하게 무의미했다.
2-8. 재앙의 시작
모나드는 빠르게 함수형 프로그래밍의 새로운 핵심 개념으로 자리잡았다. 모나드 튜토리얼들의 범람은 필연적이었다. 뉴비와 구루를 가리지 않고 수 많은 사람들이 모나드를 쉽게 가르쳐준다고 하는 이 유망한 새로운 사업에 뛰어들었다.
문제는 그것을 어떻게 가르쳐야 하는가였다. 분명 모나드는 >>=와 unit을 가진 타입 컨스트럭터라는 그것의 형식적인 정의를 아는 것만으로는 만족스럽게 이해할 수도, 써먹을 수도 없었다. 그것을 진정으로 이해시키기 위해서는 무언가 다른 것이 필요했다.
모나드의 의미를 둘러싼 신비한 횡설수설들은 실제로 모나드를 이해하는 데는 아무 도움이 되지 않았지만 모나드에 그것의 형식적 정의 이상의 의미가 있다는 것 하나만큼은 확실히 말해 주고 있었다. 대부분의 프로그래머들이 바로 거기에 그들이 모나드를 이해할 수 있게 할 단서가 있을 거라고 믿은 것은 당연했다. 타입이 어떤 의미를 가진 대상을 나타낼 경우 그 의미를 이해하는 것이 구현을 이해하는 것보다 더 중요하다는 것은 프로그래밍을 배운 사람이라면 누구나 알고 있는 상식이다.
모나드를 배우려 온 사람들은 흔히 다음과 같이 물었다.
'모나드는 무엇인가?'
물론 이 질문은 모나드의 형식적 정의에 관한 것이 아니다. 그들은 모나드가 현실의 어떤 추상적 개념을 표현한 것인가를 묻고 있었다. 문제는 아무도 이 질문의 정확한 답을 알지 못했다는 거였다. Wadler같은 고대구루들마저 그걸 알지 못해서 횡설수설하는 판에 후대의 모나드 전도사들이 그걸 대답할 수 있을 리는 만무했다.
그럼에도 불구하고 우리가 모나드를 이해할 수 있는 단서는 바로 그 질문에 있다고 믿어졌고, 당연히 많은 모나드 튜토리얼들도 그 질문에 초점을 맞췄다. 선배들이 만들어낸 계산이니 맥락이니 하는 종잡을 수 없는 횡설수설들을 그대로 반복하는 부류는 차라리 나았다. 그러한 설명이 비실질적이고 난해하다고 느낀 일부는 다른 것을 시도했다. 자신이 직접 모나드를 써 보며 느낀 모나드가 무엇인가에 대한 어렴풋한 감각을 쉬운 비유로 전달하는 것이 그것이었다.
훗날 모나드 튜토리얼 전체를 조롱거리로 전락시킨, 모나드에 대한 가장 어리석은 설명들은 이렇게 만들어졌다.
[1] Moggi, Eugenio. "Computational lambda-calculus and monads." University of Edinburgh, Department of Computer Science, Laboratory for Foundations of Computer Science, 1988.
[2] Moggi, Eugenio. "Notions of computation and monads." Information and computation 93.1 (1991): 55-92.
[3] Wadler, Philip. "Comprehending monads." Proceedings of the 1990 ACM Conference on LISP and Functional Programming. 1990.
[4] Wadler, Philip. "The essence of functional programming." Proceedings of the 19th ACM SIGPLAN-SIGACT symposium on Principles of programming languages. 1992.
[5] Newbern, Jeff. "All About Monads." Online at https://wiki.haskell.org/All_About_Monads (last accessed 19 August 2022). 2021.
[6] 생략된 부분을 찾아 읽는 것은 도움이 되지 않는다. 첫 문단의 생략된 부분에는 오직 독자들의 혼란을 늘리는 데만 기여하는 불필요한 지엽적인 부분들에 대한 지적들과 아는 척 하는 소리 말고는 아무 것도 없다. 세 번째 문단의 생략된 부분은 믿기지 않게도 첫 번째 문단의 것보다 더 무가치하다.
[7] Online at https://en.wikipedia.org/wiki/Monad_(functional_programming) (last accessed 19 August 2022). 2022.
[8] C. A. McCann's answer (Jul 23 '10 at 23:39) on "How and why does the Haskell Cont monad work?" Online at https://stackoverflow.com/questions/3322540/how-and-why-does-the-haskell-cont-monad-work (last accessed 19 August 2022). 2010.
------------------------------------------------------------------------------------------------------------------------------------------------------------------------
여기까지가 파트 1이고 길어져서 짤름. 파트 2 부터는 아직 안써서 한달 이상 걸릴 수 있으니 기다려주길 바람.
살면서 나름 장문글 많이 써본 편인데 빡세기로 이게 탑인듯. 빨리 안써지네.
예전에 깃갤에서 모나드 강의 만든다고 해놓고 그동안 뭐하고 있었는지 궁금한 갤러들 있을텐데 대충 이거 쓴다고 뻘짓거리하고 있다고 보면 된다.
몰라. 나도 사실 이거 왜 하는지 모르고 그냥 광기로 쓰고있음.
지금까지 모나드 튜토리얼들 보면서 좃같았던 것들 좀 시원하게 까보고 싶었다.
반박받는다. 이 주제에 관한 한 어떠한 논쟁에도 참여할 의사가 있으니 고수들 읽어보고 글의 내용에 이견 있으면 말해줬으면 함.
모나드추 스타도 박았다 ㅇㅇ
역사적 배경은 처음알았다. 진짜 정성글 대박.
개추 - dc App
왤케왤케 양질임
https://m.dcinside.com/board/github/39739
길어져서 글로 씀
모나드가 타입 컨스트럭터라는 부분에서 거릅니다 ㅅㄱ
도대체 모나드가 어떻게 타입 컨스트럭터가 아니냐? 뭔 오개념을 가지고 있길래 이렇게 되지?
join 대신 bind를 가지고 노는 이유도 모르면서 모나드를 다 아는 듯이 말해요?
do 노테이션이 얼마나 소중한지도 알기나 해요?
도대체 글을 어떻게 읽으면 이 글을 보고 내가 join 대신 bind를 가지고 노는 이유를 모른다는 결론이 나오지? 희한한애네;;;
그리고 이거 파트 1이야;; do 노테이션 얘기 나중에 다 나온다. 내가 do 노테이션을 무시했으면 내 모나드 강의에서 거기에 그만한 분량을 할애했을까?
"왜 계산들의 합성 수단이 꼭 T<A>->(A->T<B>)->T<B> 타입의 함수로 주어져야 하는가? 계산의 합성 수단이 필요했다면 (A->T<B>)->(B->T<C>)->(A->T<C>) 타입의 더 직설적이고 일반적인 함수를 요구하는 정의를 쓰는 것이 명백히 더 자연스럽지 않은가?" 이딴 개소리에 대한 답임
OK. 뭔말하는지는 대충 알겠음. bind 가지고 그 질문 대답하는 게 불가능하진 않지. 더럽게 복잡해서 도저히 모나드 처음 배우는 사람한테 설명할 길이 없어서 문제지. 그 문장의 문맥은 이해하고 있는 거 맞지? 모나드를 '처음 배우는 사람한테' bind 기반 모나드의 정의가 그것이 계산을 나타내기 위해 만들어진 것으로 보기엔 너무 임의적으로 보인다는 게 포인트다. 당연히 내가 제기한 질문들이 자연스럽게 제기될 수 있는거고. 님 말은 bind 가지고 그거 대답할 수 있다는 건데 한번 해보길 바람. 가능하다는 건 아는데 쉽게 설명하기가 쉽지 않을걸?
그건 맞음. join이 개념적으로는 쉬운데, bind를 너무 깎아 내리는 것 같아서 그랬음.
도움 많이 받습니다 개추개추 천천히 읽어볼게욤
감사합니다 선생님ㅠㅠㅠㅠ
역시 모나드를 설명하는건 어렵
그래도 내가 읽은 모나드 글중 가장 이해가 되는 기분임
너가 보기에는 함수형이 5년 안에 세상 재패할거같냐?
어렵다.
감사히 읽었습니다