하스켈 입문자들한테 기본 모나드 중 뭐가 가장 이해안되냐고 물어보면 십중팔구 Cont랑 IO 얘기할거다.
Cont야 안쓰면 된다 쳐도 IO는 안 쓸 수가 없는데 이해가 안되니까 문제다.
모나드랑 do/for 문법에 대해서는 이 글에서 다뤘고 Cont 모나드는 이 글에서 다뤘다. 이번 글에서는 IO 모나드를 좀 더 자세히 다루어 보기로 한다.
(1) 상상도 못한 정체
모나드 튜토리얼들 보면 IO 모나드가 뭔지를 유독 애매하게 다루는 경우가 많은데 이유는 간단함. 그냥 가르쳐주는 애들도 이게 뭔지를 잘 모른다.
모르니까 이악물고 활용얘기만 하는거.
나는 물론 IO〈T〉의 의미를 바로 선언하고 시작할거다.
T 타입의 값을 반환하는 인자 없는 I/O 프로시저
부작용을 가질 수 있는 명령형 언어들의 함수를 프로시저라고도 한다는 건 알거임. 그 프로시저 맞다.
I/O 빼고 말해도 됨. 'T타입의 값을 반환하는 인자 없는 프로시저.'
함수형 언어의 함수들은 순수해야 하는데 그럼 함수만으로는 프로그램을 만들 수 없으니까 따로 프로시저 타입을 만든거다.
우선 이게 모나드가 맞는지부터 확인해 보자. lift와 unit과 flat을 정의할 수 있는지만 보면 되겠지.
언제나처럼 자바 비스무리한 의사 언어를 사용할거다.
이 언어는 프로시저랑 함수 모두 정의할 수 있는데 프로시저는 다른 프로시저 내부에서만 부를 수 있으며 타입 앞에 _proc 접두사가 붙는다.
IO〈T〉는 _proc () → T를 감싸는 래퍼 타입이 된다.
편하게 람다로 정의할것임. T타입의 값 t를 넣었을 때 f(t)가 나오는 함수를 (T t) => f(t) 라고 쓰는 문법이다.
lift는 이렇게 정의하면 된다.
unit은 이렇게,
flat은 이렇게 정의하자.
워낙 쉬운 코드들이라 딱히 설명할 게 있을까 싶다.
다만 이렇게 IO 모나드의 기본 함수를 직접 구현하는 건 프로시저가 있는 언어에서나 가능한거고 아예 함수만 있는 언어에서는 안된다.
하스켈에 IO 모나드의 구현이 없는건 그런 이유 때문이라고 보면 되겠다.
(2) 불가능을 가능으로 만든 IO 모나드?
아마 대부분의 사람들이 모나드에 대해 궁금해하는 건 그걸 가지고 어떻게 I/O를 하는 프로그램을 순수하게 프로그래밍할 수 있느냐일 거다.
모나드가 대단해 봐야 함수 몇개 달린 타입 컨스트럭터일 뿐인데
어떻게 순수하지 않은 프로그램을 순수하게 만드는 형용모순같은 짓을 저지를 수 있는지를 다들 궁금해한다.
미스터리는 그걸 하는 하스켈의 main을 보면 바로 풀린다.
다른 언어의 main은 함수지만 하스켈에서는 IO〈()〉타입의 값이다. (하스켈 표기로는 IO ())
_proc () → () 타입의 프로시저라는 거지.
결국 하스켈도 프로시저를 쓰니까 순수하지 않은 거 아니냐는 의문이 들텐데 그렇지는 않다.
코드에서 프로시저들을 호출할 수가 없기 때문이다.
대신 합성이 가능하다.
IO〈T〉가 T를 반환하는 인자 없는 프로시저니까 당연히 T에서 U로 가는 인자 있는 프로시저는 T → IO〈U〉타입의 함수로 나타낼 수 있는데
이전에 설명한 모나드의 성질에 의해 이런 것들을 합성할 수 있다.
이른바 하스켈 프로그래밍이라는 건 기본 I/O 프로시저들을 레고마냥 조립해서 main을 얻는 과정일 뿐임.
언뜻 보면 명령형 코드처럼 보이는 다음 코드도 마찬가지다.
이걸 do 표기법 없이 쓰면 이렇게 된다.
getline은 IO〈string〉 타입이고 putStrLn은 string → IO〈()〉 타입이며 >>=는 모나드 M에 대해 M〈T〉랑 T → M〈U〉 합성해주는 하스켈 연산자다.
그냥 _proc () → string 이랑 _proc string → () 이랑 합성해서 main 만드는 코드라는 걸 알 수 있다.
결론적으로 하스켈 코드는 순수한 게 맞다.
그런데 어떻게 그걸로 I/O를 하는 프로그램을 만들 수 있냐, 코드가 프로그램으로 컴파일되는 게 아니기 때문이다.
하스켈에서 main = 우변의 코드는 main의 본문이 아니다. main을 조립하는 코드고 실행시간에 실행될 필요조차 없음.
다른 데 있는 코드도 마찬가지다. 모두 컴파일 시간에 실행되고 그 결과로 조립된 main만 프로그램으로 컴파일돼서 실행시간에 실행되는 것이다.
(3) 결론
IO 모나드는 어렵지 않다. 내가 보기엔 State 모나드 같은것보다도 이게 더 간단함.
근데 왜 어렵다고 소문이 났냐, 설명을 이상하게 해서임.
IO 모나드에 대한 설명 태반이 IO〈T〉를 World → (T, World)로 설명하던데 뭐하는 짓인지 모르겠다. 이거 말이 되기는 함?
상태 프로세스야 같은 초기상태에 대해 같은 결과값이랑 결과 상태를 주면 다 똑같다고 할 수 있으니까 S → (T, S) 로 나타낼 수 있지만
I/O 프로세스는 중간에 뭘 하느냐에 따라 다 다른 프로세스인데...
뭐 World에 사용자 기억까지 포함한다고 치면 관점에 따라 될 것 같기도 하다.
근데 IO 모나드를 모르는 사람은 무조건 오개념 생긴다.
맞는지도 의심스럽고 배우면 무조건 오개념 생기는 설명을 왜 하는 거지? 도대체 왜?
하스켈의 IO 액션과 본질이 같은 개념 중 대다수의 프로그래머들에게 가장 친숙한 개념은 명령형 언어의 프로시저다.
모든 프로그래머들이 이거에 너무나 친숙한 나머지 그냥 IO 액션을 프로시저로 설명하기만 하면 그것의 개념에서 활용까지 모든걸 일사천리로 이해하는데
다들 세계의 상태 모나드 이딴거로 외도함. 보면서 빡이 쳐 안쳐?
구현이 필요하면 그냥 잠깐 프로시저 정의문이 있다고 치고 의사 코드로 설명하거나 프로시저가 있는 언어로 설명하면 되는데
그렇게 하면 IO 모나드가 왜 모나드가 되는지, 왜 하스켈은 IO 모나드 구현을 보여주지 않는지를 다 답해줄 수 있는데 그걸 나빼고 아무도 안한다.
나도 한때는 세계의 상태 모나드 헛소리의 피해자였음. 그때 엉터리 설명때문에 오개념 생겨서 고생했던거 생각하면 아오
죽창이 필요하다...
하여간 IO 타입은 그냥 프로시저 타입이라고 이해하면 쉽다.
물론 모나드 규칙을 만족하느냐를 따지기 시작하면 좀 복잡해지는데 왜 따져 이걸.
굳이 따지자면 프로시저 두개가 실행속도까지 완전히 같아야 같은 거라면 이 글의 구현대로는 안 된다.
가령 iot가 즉시값 t를 반환하는 프로세스면 lift(f)(iot)도 즉시값 f(t)를 반환하도록 lift를 최적화해야 되는데 이걸 할려면 리스프 정도 되는 언어가 필요함.
...그냥 되게 할 수 있다는 것만 알아두도록 하자.
모나드 관련글은 이게 마지막임. 지긋지긋하기도 하고 내가 아는게 좁아도 모나드 원툴까지는 아니다.
github의 강의 다 완성되면 짧게 홍보정도나 할거임.
애초에 내가 자신있는건 프로그래밍의 특정 주제라기보다는 나만의 사고법, 접근법, 아이디어 같은 것들이고
모나드 강의도 그냥 다수가 안된다고 하지만 내가 보기엔 될 것 같아 보이는 주제에서 내 접근법이 얼마나 통하는지 테스트해보려고 뛰어든 것뿐인데
병신이 이게 좀 될 각 보이니깐 또 그 접근법 자체에 심취해서 병신짓함.
나도 중요한 걸 잊고 있었던 것 같은데
일단은 자숙의 시간을 좀 가져야겠다. 언젠가 다른 쓸모없지만 재미있는거 다루는 글로 돌아오겠음.
다뤄줬으면 하는 주제가 있으면 제안해도 됨. 하스켈 말고 다른거로 부탁한다.
(1)의 코드는 그냥 말장난임. IO 타입은 명령형 언어의 함수(=procedure)를 순수 함수형 언어에서 표현하려고 나온 개념이고, 이걸 설명할 때의 문제는 이제 IO 타입이 다른 언어의 procedure와 미묘한 차이가 있다는 점인데 저건 그냥 IO랑 똑같이 동작하는 proc이라는 타입을 임의로 가정하고 그걸로 IO를 설명한거잖아.
가령 flat 정의는 그냥 bind로 join 구현했다는 소린데 아무 모나드에서나 다 되는거잖아. 저럴거면 차라리 bind 가지고 설명하던가
IO를 더 엄밀하게 정의할거 아니면 그냥 "IO 타입은 procedure를 함수형 언어에서 표현한 것이다" 정도로 설명하고 모나드가 procedure에 대해 말이 되는 인터페이스다 언급하는 정도가 적당하다고 생각함
IO 타입과 다른 언어의 procedure 사이의 미묘한 차이가 뭐임? 나는 둘 사이에 실질적인 차이가 없다고 보았는데 그 부분 좀 설명 부탁함.
확실한 건 나는 IO랑 똑같이 동작하는 proc이라는 타입을 임의로 가정하지 않았음. 'IO 타입과 다른 언어의 procedure 사이의 미묘한 차이' 가 없다는 가정 하에 님이 말하는 다른 언어의 procedure와 똑같이 동작하는 proc이라는 타입을 임의로 가정한것임. 자바처럼 프로시저가 보통인 언어에서 IO 모나드를 구현한다면 proc은 빼버려도 됨.
IO 타입이 first class다, procedure call의 개념이 없다 이게 두가지 중요한 차이라고 생각함
일단 하스켈 말고 다른 언어의 프로시저 중에서도 first class인 것들은 수두룩하고 우리가 흔히 프로시저라고 할 때는 first class인 것들이랑 그렇지 않은 것들을 통틀어서 말함. 그걸 하스켈 IO와 다른 언어의 procedure 사이의 미묘한 차이라고 할 수 있는지 의심스러움. procedure call의 경우 나는 글에서 하스켈의 IO는 코드에서 호출 불가능하다는 걸 강조하면서 그것때문에 생길 수 있는 오개념은 충분히 방지했다고 보는데.
무엇보다 스칼라의 cats.effect.IO나 scalaz.effect.IO같은 것들도 IO 모나드라고 함. 세세한 최적화 부분에서는 차이가 있을지 모르겠지만 전부 나처럼 그 언어의 프로시저를 가지고 만들지. 즉 나는 스칼라 등 하스켈 외의 언어에서 일반적으로 채택되는 IO 모나드의 가장 단순한 구현을 그대로 보여준 것 뿐인데 (다시 말하지만 세세한 부분에서 최적화의 차이는 있을 수 있음) 이게 왜 말장난이라고 불려야 하는지 이해할 수 없네.
당연히 개중에 하스켈과 거의 똑같이 이해할 수 있는 procedure를 가진 언어도 있겠지;; 개별 언어하고 일대일로 비교하면 차이가 있을 수 있다는 소리잖아. 그냥 procedure라고만 얘기하고서 독자가 first class procedure가 있는 언어를 떠올릴 것이라고 가정하면 안되지.
호출이 불가능하다고 말하는건 두가지 맥락이 있는데 하나는 function에서 procedure 호출이 불가능하단 소리고 (이건 purity를 위해 필요한 것), 다른 하나는 procedure에서 다른 procedure를 호출하는 개념이 없다는 것 (대신 bind로 합성된다). 글에서는 이 두가지에 대한 명시적인 구분이 없음.
나는 굳이 내 의사 언어에서 프로시저가 first class임을 굳이 설명하지 않아도 독자들이 내 의사 코드를 보고 혼란을 느끼지도 않을 거라고 생각했는데 주딱 생각은 다르다는 거네. 이건 논쟁으로는 결론날 수 있는 문제가 아닌듯 싶다.
그래도 글의 (2)는 어디까지나 하스켈에 대한 얘긴데 대체 하스켈에서 procedure에서 다른 procedure를 호출하는 개념이 있는지를 왜 따져야 하냐고;;; 하스켈에서는 procedure, 즉 IO 액션을 본문을 넣어 직접 정의할 수 있는 방법 자체가 없잖아. 당연히 procedure에서 procedure를 호출할 수 없지 procedure에 들어가지를 못하는데. 뭔가를 초보자들한테 쉽게 설명하려면 과감하게 단순화할건 단순화하고 넘어갈건 넘어가야 한다. 그런 것까지 다 구분해서 글에 일일히 주석으로 넣으면 독자들의 이해도는 더 떨어질듯.
그런 부분까지 초보자에게 다 설명하는게 과하다는 의견은 나도 동의하고, 그래서 내 의견은 ["IO 타입은 procedure를 함수형 언어에서 표현한 것이다" 정도로 설명하고 모나드가 procedure에 대해 말이 되는 인터페이스다 언급하는 정도가 적당하다] 임. 저렇게 설명하는건 IO로 IO를 설명하는거나 다름 없다고 생각하니까.
내 의견은 이미 IO 모나드가 하스켈만의 개념이 아니고 스칼라 등 이미 프로시저를 기반으로 IO 모나드를 구현하고 있는 언어가 있는 이상 그러한 일반적인 의미에서의 IO 모나드를 설명해야 한다는 것임. 프로시저가 존재하며 함수와 프로시저 모두가 fist class인 모든 언어에서는 IO 모나드를 구현할 수 있음. 스칼라가 그랬고. 내가 잘못한 게 있다면 IO 모나드의 프로시저 기반의 구현을 보여준 게 아니라 내 의사 언어에 대해 설명할 때 "여기서는 함수랑 프로시저 모두가 fist class다" 라는 중요한 설명을 빼놓은 것 정도라고 본다.
주딱 말대로 IO 타입은 first class다, procedure call의 개념이 없다라는 점에서 프로시저와 다른게 맞음. 그러니까 first class인 procedure를 지원하지만 procedure call의 개념도 존재하는 언어에서 그걸 IO 타입으로 캡슐화해서 "procedure call의 개념이 없는 procedure 타입", 즉 IO 액션 타입을 만드는 것 아닌가. 나는 그 과정을 보여준것일 뿐이다. 여기서 procedure에서 IO 액션으로의 변환은 결코 의미없는 과정이 아님.
다만 여기서 함정이 있다면 스칼라에서 구현된 IO 모나드들은 procedure call이 있다는 거지만. (당연하지, 언젠가는 호출해야 하잖아!) IO 모나드 자체를 일반적인 개념으로써 어떻게 정의할거냐의 문제가 있는데 난 그 "procedure call의 개념이 없다" 도 반드시 필요한 건지는 모르겠음.
음 procedure call이 있는 언어로 IO 모나드를 설명했다는 해설은 말이 되는것 같다
내가 procedure call이 없는게 중요한 성질이라고 생각하는 또 다른 이유는 하스켈에 (C같은 언어의) return이 없기 때문임. 이런 언어에서는 procedure는 'call할 수 있는 대상'이고, 자체적인 스코프를 가지지 (call이 곧 다른 스코프로 넘어간다는거고). C 언어의 return은 그 스코프를 탈출하는건데 하스켈은 procedure가 자체적으로 가지는 스코프가 없으니 그런게 없는 것.
명령형 프로그래밍 언어 형식화 모델중에 interaction tree라는게 있는데, itree라는 모나드를 수학적으로 엄밀하게 정의하면 이게 C언어의 함수 같은 개념의 semantics가 됨. C언어 안에서의 procedure는 좀 syntactic한 요소들이 많이 있고 (방금 말한 스코프라던가) 여기서 semantics를 뽑아내면 그런게 다 날아감. 근데 하스켈의 IO는 C언어의 함수 보다는 itree 쪽에 가까움. 이런 면에서 차이가 있다고 느꼇다 나는.
그나자나 다른 것도 하나 오해한 것 같은데 내가 flat을 정의할 때 bind를 썼다는 건 무슨 얘기임? ioiot()()는 그냥 ioiot가 _proc () → _proc () → T 타입이니까 그걸 한번 call 해서 _proc () → T 를 얻고 그 결과를 다시 call 해서 T를 얻는 코드일 뿐인데 뭔가 오해가 있는 거 아니야? 글에도 설명되어 있지만 내 IO 타입들은 ()를 붙여서 호출 가능함.
글에 설명을 했다고 생각했는데 안되어 있었네;
그거는 proc을 모나드로 이해하면 결국 bind라는 소리
(2)에서 main을 조립하는 코드는 컴파일 타임에 실행된다? 이건 그냥 말도 안되는 소리임. 튜링 완전하고 무한루프를 돌 수도 있는데 어떻게 컴파일 타임에 완전하게 실행되냐
좀 사소한 부분이지만 _proc string -> () 와 string -> _proc () -> () 가 동등하다는 것도 제대로 설명하지 않고 있고
나는 컴파일 타임에 실행된다고 생각했는데 그게 틀렸으면 그 부분은 내가 틀린걸 인정. 보통은 컴파일 타임에 다 실행되는게 아닌거임? 무한루프를 도는 예외적인 경우에만 내 설명이 틀렸다고 하는 거면 그거야말로 지엽적인 꼬투리 잡기로 느껴지는데.
무한루프가 spin만 말하는게 아님. "hello"를 무한히 출력하는 프로그램을 IO 타입의 값으로 보면 print "hello"; print "hello"; ... 이게 무한히 이어지는건데, 이건 컴파일 타임에 가능한 계산이 아니지
ㅇㅇ 알겠고 이건 내가 잘못 생각한것 같다. 그러면 하스켈 코드는 보통 언제 실행됨? 컴파일 시간에 실행할 수 있는 건 최대한 컴파일 시간에 실행하는거 맞지? 보통의 프로그램에서는 어떰?
그냥 다른 프로그래밍 언어하고 똑같음. 기본적으로 계산은 런타임에 일어남. constant folding이 일종의 컴파일 타임에 실행이라고 볼 수 있긴 하지만, 명령형 언어라고 이런 최적화를 안하는것도 아니니까
질문: _proc string -> () 와 string -> _proc () -> ()가 왜 같아요?
ocaml에서는 string -> ()랑 string -> () -> ()랑 다를 텐데요...
그냥 a -> IO b를 _proc a -> b로 본 건가...
동등하다는 게 같다는 얘기는 아님요. (T, U) -> V랑 그걸 커링한 T-> U -> V가 동등하지만 같지 않은 것처럼. 일단 _proc string -> () 는 partial applied function 만들때랑 똑같은 방법으로 string -> _proc () -> ()로 만들 수 있음. partial applied procedure를 만든다고 해야 하나 하여튼 똑같은 미리 인자 넘기기임.
본문의 구현에 의하면 IO〈U〉는 _proc () -> U라서 T -> IO〈U〉는 엄밀히 말하면 T -> _proc () -> U 고 _proc T -> U 하고는 다름. 본문에서는 그냥 동치니깐 변환 과정 없이 T -> IO〈U〉는 _proc T -> U다 하고 넘긴겁니다. 다믜님은 그래도 이건 설명해야 하지 않냐 하는 입장이 약간 있는거고 전 이정도는 생략해도 된다는 입장인거고.
그런 얘기는 haskell 같은 느긋한 언어에서의 의미론에서 먹히는 소리 아닌가요? ocaml 생각해보면, 동등하지 않는 걸로 알고 있어요. 그래서 이런 언어들 때문에 저 설명이 맞는지 모르겠어요.
ocaml에서는 왜 동등하지 않은거에요;? 그냥 T -> _proc () -> U를 _proc T -> U로 바꾸는 함수랑 _proc T -> U를 T -> _proc () -> U로 바꾸는 함수랑 둘 다 쉽게 정의되던데... 변환한 다음 다시 변환한 결과도 원본 프로시저랑 차이가 없고.
제가 보기엔 _proc string -> () 을 string -> _proc () -> () 로 바꾸는 과정은 그냥 프로시저에 인자 집어넣는 부분을 분리하는 거고 이거랑 lazyness랑 관련이 있을 이유가 없음. 만약 제가 틀렸다면 이건 꼭 알려주셨으면 함.
애초에 ocaml의 string -> () -> ()에 대응되는 건 _proc string -> _proc () -> () 이라고 봐야하지 않나. 그건 당연히 다르지.
이흫// 님 말이 맞음
(3)에서 IO T를 World -> (T, World)로 설명하는건 나도 좀 비판적인데, IO 내부구현이 저렇게 생겨먹어서 나온 설명인듯. 물론 실제로 저게 말이 되기 때문에 구현이 저런게 아니라 그냥 트릭같은거지만.
아무튼 그렇기 때문에 (1)에서 하스켈에 IO 모나드의 구현이 없다는 말도 엄밀히는 사실이 아님. 구현은 있음. 그냥 컴파일러 primitive를 노출시킨것에 가깝기는 하지만.
내가 항상 설명을 위해 단순화시키는 부분이 있음. 그것 하나하나에 대해 너무 지엽적으로 꼬투리잡고 있는것 아닌가 싶다.
lift(f)(return t) = f(t) 이거는 IO를 프로그램 specification 관점에서 이해하면 별로 신경쓰지 안아도 되는 문제. 프로그램의 실행시간이 어떻게 되야한다고 요구한게 아니니까. 애초에 형식적인 정의 없이는 저런 얘기를 하기 힘들고
꼬투리 잡는게 아니라 동의를 표현하고 보충설명을 한건데 이렇게 반응하니 당황스럽다
ㅇㅇ 그런 거라면 미안함. 약간 까칠하게 반응했네.
글쓴게이랑 주딱댓글 감탄나오노 ㅋㅋ 이게 깃갤이지. 둘다 수준높은 논쟁하는거 왤케 ㅈㄴ 멋있음 깃갤뽕 ㅈㄴ찬다ㅋㅋ
순전히 뇌피셜인데 본인은 모나드를 어떻게 이해했냐면 수학의 함수랑 프로그래머들이 함수라면서 작성하는건 딱봐도 뭔가 많이 다르거든 그냥 딱보는게 아니고 그게 왜 어떻게 다른가를 수학자 아재들이 학문적으로 정의해놓은게 모나드인거 같음 뭔가 기존에 없던 새로운걸 내놓은게 아니고 사람들이 하던걸 묘사해놓은거란 생각임 그걸 알면 그동안 내가 뭔짓을 했나 이해가 생길 수 있으나 딱히 모른다고 해서 그동안 하던걸 못하는 것도 아닌 뭐 그런
근데 lift에서 IO<U>(_proc () => f (iot()))이어야 하지 않나? () => f (iot ())가 아니라 _proc이 앞에 있기 때문에
∘를 함수 피연산자에만 유효한 기호라고 보면 그렇긴 한데 프로시저에도 유효한 연산자라고 보면 문제가 될게 있을까 싶음. 이 경우 f∘g는 f와 g가 모두 함수면 함수, 둘 중 하나라도 프로시저면 프로시저가 되겠지. 의미는 님이 말한대로.
다시 읽어보니까 글이 IO 모나드의 핵심 제대로 짚고 있다는 생각이 듦
내가 IO 모나드 헤맬 때가 언제인지는 기억이 나지 않지만, 이 글을 읽었으면 그렇지 않았을 것
근데 나도 컴파일 시간에 실행된다는 건 매우 이상하다는 생각임
컴파일 타임에 실행된다고 말하면 당연히 오해의 소지가 큼
정확히는 오해의 소지가 아니라 틀린 말이고, 올바르게 고쳐야 하지만...
본문에 말했듯 나는 main이라는 IO 프로세스를 하스켈 코드에 따라 조립하는 것까지는 컴파일 시간에 실행되고 완성된 main 프로세스만 실행시간에 실행된다고 생각했음. 그 부분은 확실히 오류 인정.
그렇게 말해야지 본문처럼 말하면 오해하기 좋다.
왜 오해하기 쉽냐면, 그렇게 치면 다른 언어들도 컴파일 타임에 조립되고 런타임에 실행돼서이다.
그 부분은 똑같이 말했다고 생각하는데. 본문에서 이렇게 말했잖아. [하스켈에서 main = 우변의 코드는 main의 본문이 아니다. main을 조립하는 코드고 실행시간에 실행될 필요조차 없음. 다른 데 있는 코드도 마찬가지다. 모두 컴파일 시간에 실행되고 그 결과로 조립된 main만 프로그램으로 컴파일돼서 실행시간에 실행되는 것이다.]
다른 언어의 main 정의하는 코드는 main의 본문인 반면 하스켈 코드는 main의 외부에서 그걸 조립하는 코드니까 엄연히 차이는 있지. "getline >>= purStrLn" 은 main을 실행시켰을 때 이루어지는 계산이 아니잖아. 그 전에 main을 얻기 위해 이루어지는 계산이지.
다믜님 지적은 그 main 이라는 IO 프로세스를 조립하는 것 자체도 컴파일 시간에 완수될 수 없다는 거고.
IO가 마치 씨플플 템플릿처럼 조립된다고 글을 쓴 거임?
컴파일 타임에??
그러니까 이 글의 2번은 main의 외부에서 main을 조립한다는 내용이고 다믜님이 반박한 거고 그걸 님이 수긍한 거다?
가령 (2)의 예제로 예를 들자면 getline >>= purStrLn가 컴파일 타임에 계산된다는 얘기임. 그렇게 계산된 main이 다시 프로그램으로 컴파일되고, 실행시간에 실행되는 건 그 main이고. 간단한 예제에서는 그게 더 그럴듯하기 때문에 복잡한 경우까지 생각 못하고 일반적으로 그럴 거라고 생각했음.
다믜님 얘기는 그 main 계산이 항상 컴파일 타임에 완수될 수 없다는 거지.
그 계산이 시뮬레이션 비슷한 걸 말하는 건가? 그럼 당연히 틀린 거지...
아니 왜 시뮬레이션을 해. getline >>= purStrLn 은 그냥 getline 이라는 IO string타입의 값이랑 purStrLn 이라는 string -> IO ()타입의 값에 >>= 라는 수학적인 연산을 적용시켜셔 IO () 타입의 값을 구하는 아무런 부작용을 일으키지 않는 수학적인 계산이잖아. 그렇기 때문에 순수하고. 그 순수한 계산이 컴파일 시간에 이루어진다는 거지 컴파일 시간에 getline이나 putStrLn이 실행된다는 얘기가 아니잖아.
그 계산을 컴파일이라고 부르지 않나?
가령 다른 언어로 같은 프로그램을 짠다고 해봐. main의 함수 정의 안에 a = getline(); putStrLn(a)(); 같은 명령형 코드가 들어가겠지. a = getline(); putStrLn(a)(); 를 하는 것도 컴파일이라고 부름? 그건 실행이지.
혹시 하스켈 구현 대충 알아보기는 함? 구라 안 치고 IO a = World -> (a, World)이고, 네가 말한 대로 bind도 적절히 치환됨.
다른 언어의 main정의하는 코드는 main의 본문이니까 main을 호출한 이후 실행될 실행 명령들이 담겨 있는 거고. 하스켈의 main= 오른쪽에 오는 코드는 main을 계산하는 식이니까 원칙적으로는 오히려 main의 전에 계산되는 게 맞지. 그걸 계산을 안하면 main이 구해지지가 않잖아. 다만 나는 그 계산이 컴파일 시간에 완수된다고 생각했던 거고 주딱 얘기는 아니라는 거고.
하스켈 IO a가 World -> (a, World)인지 아닌지 그렇다면 어떻게 그렇게 되는지는 나도 흥미가 있는 주제긴 한데 다른 코멘트로 달아주길 바람. 주제가 이상한데로 엇나가는 것 같음.
main이 구해진다는 표현부터 이해가 안 가긴 함.
일단 뭘 알려주고 싶은지는 알았고, 이건 그냥 너가 "사실 하스켈의 main IO 는 계산되지 않음 (컴파일 시간에도 실행 시간에도!) 이거 읽어보셈" 하고 그 자료 링크 주기만 했으면 해결될 문제였음.
그러니까 결국 내가 원하는 건 다믜님과 님의 대화를 이해하는 건데
자꾸 내 말을 하지도 않은 말로 곡해하는 것 같아서 짜증낸 거임.
그 점 미안함. 아니, 이게 내가 알고 있는 지식이랑 자꾸 충돌하면 어떻게 사고해야 하는지를 모르겠음
다 이해한 것 아니었어? 난 컴파일 시간에 main IO 의 계산이 완전히 이루어지고 실행 시간에는 그게 실행되기만 하는 거라고 생각했었음. 그런데 내가 너무 쉽게 생각한 거였지. 그냥 그게 전부임.
형님 함수형 제대로 배울라면 어케항요