CODEONWORT

[ALGOSPOT] MERCY - IO 모나드가 뭐에요 본문

Season 1/하스켈

[ALGOSPOT] MERCY - IO 모나드가 뭐에요

codeonwort 2014.10.03 15:03
하스켈을 어떻게 배울까 생각하다가 알고리즘을 풀면서 배우면 좋을 것 같아서 하스켈을 지원하는 온라인 저지를 찾아봤다. ALGOSPOT 이라는 사이트에서 하스켈을 지원하는 걸 발견.

가장 간단한 문제인 MERCY를 풀어봤다. 10 이하의 정수 N을 입력받아 Hello Algospot! 을 N번 출력한다.

위키책을 번역하면서 배운 지식을 활용해보면..

1. getLine으로 문자열 c를 입력을 받는다.
2. read로 c을 숫자 n으로 변환한다.
3. replicate로 "Hello Algospot!"을 n번 복제한다.

[시도 1]
s = "Hello Algospot!"
main = do
        cnt <- getLine

        print $ replicate (read cnt) s


3을 입력해보면...

["Hello Algospot!","Hello Algospot!","Hello Algospot!"]


이게 아닌디 -_- join을 쓰면 되나?

 [시도 2]
s = "Hello Algospot!"
main = do
        cnt <- getLine

        print $ join "\n" (replicate (read cnt) s)


 다시 컴파일해보니

main.hs:4:17: Not in scope: `join' 


join 함수가 Data.List.Utils 모듈에 있다는데 코드에 import Data.List.Utils를 넣어봐도 없는 모듈이라 나온다. 모듈도 못 불러오는 멍청이라니

물론 한 문자열을 n번 출력하는 재귀 함수를 직접 만드는 것은 쉽다. 하지만 위키책을 읽으면서 느낀 게 이쪽 바닥은 기본 라이브러리에 있는 기능으로 해결되는 걸 패턴 매칭이나 재귀로 직접 푸는 걸 죄악으로 여기는 것 같으니 어떻게든 라이브러리 함수를 써서 풀어보자. 이렇게 된 이상 replicateM으로 간다.

[시도 3]
import Control.Monad

s = "Hello Algospot!"
main = do
        cnt <- getLine

        replicateM (read cnt) (print s) 


그 결과는

"Hello Algospot!"
"Hello Algospot!"
"Hello Algospot!" 


여기까지 인터프리터로 실험한 다음 당당하게 제출했으나 오답(?)

이제보니 Hello Algospot! 이 아니라 "Hello Algospot!" 이 출력된다. 알고보니 print가 아니라 putStrLn을 써야 했다...

[시도 4]
import Control.Monad

s = "Hello Algospot!"
main = do
        cnt <- getLine

        replicateM (read cnt) (putStrLn s) 


이렇게 통과는 했지만 아직 모나드의 작동 방식이 익숙하지 않아서 연습삼아 do를 없애보기로 했다.

main = getLine >>= some_func


getLine의 타입은 IO String이다. 그러면 some_func는 인자 하나를 취해 그걸 출력하는 IO 액션이어야 한다...가 맞나? 모나드 초짜라서 아예 다 따져보자면, IO는 Monad 클래스의 인스턴스다. 그리고 (>>=) 연산자를 구현한다. 이 연산자의 타입은...

(>>=) :: Monad m => m a -> (a -> m b) -> m b

이다. m a는 getLine, (a -> m b)는 f, m b는 반환값이 된다. x >>= y 의 의미는 모나드 x 내부의 값을, y가 접근하여 또다른 모나딕 값으로 평가한다는 것이다. 즉 getLine >>= f 라고 쓰면 f는 String 값을 취해 IO b 타입의 값을 반환하는 액션이어야 한다.(b는 매개화 변수)

그런데 getLine의 타입이 IO String이므로 m a를 이걸로 치환하면?

Monad IO => IO String -> (String -> IO b) -> IO b

깨달음이 조금씩 오는 것 같다. some_func는 인자 하나를 취해야 하는데 그 인자의 타입이 String이라는 뜻이다. 그 결과는 또다른 IO 액션이다. 결과의 타입이 IO () 이든, IO String 이든 IO b 꼴이라는 것이다.

main = getLine >>= (\ c -> putStrLn "Hello Algospot!")


이렇게 하면 문자열을 입력받기만 하고 Hello Algospot! 을 한 번만 출력하는 프로그램이 된다. 잘근잘근 뜯어보니 모나드가 이제서야 조금씩 이해되는 것 같기도 하다. 이제 replicateM을 써서 [시도 4]와 동등한 코드로 만든다.

import Control.Monad

s = "Hello Algospot!"
main = getLine >>= (\ c -> replicateM (read c) (putStrLn s))

헬로월드 수준의 프로그램을 만드는 게 이따구로 어렵다니 -_- 근데 재밌다
1 Comments
댓글쓰기 폼