Graphics Programming

IO 모나드와 Functor 본문

Season 1/하스켈

IO 모나드와 Functor

minseoklee 2014. 11. 1. 11:00
콘솔에서 숫자를 읽을 때 귀찮은 점은 문자열로 읽어서 변환을 해줘야 한다는 것이다. 예컨대 온라인 저지의 알고리즘 문제들은 보통 입력될 문제의 개수 C를 제시하고 C개의 테스트 입력들을 제시하기 때문에 이런 식으로 코딩하게 된다.

import Control.Monad

main = do
  numCases_s <- getLine
  let numCases = read numCases_s :: Int

   replicateM_ numCases $ do
    -- 여기부터 알고리즘


표준 입력에서 "123", "45" 같이 텍스트로 표현된 숫자를 입력받아서 숫자형으로 변환하는 것이기 때문에 애초에 콘솔에서 숫자형으로 읽을 수는 없다. 하지만 두 줄에 쓰려니 변수 이름도 두 개를 써야 해서 불편한데...

그냥 numCases_s <- getLine 이라고 쓰면 numCases_s의 타입은 IO String이 된다. 여기에 read를 어떻게든 적용해서 IO String -> IO Int로 변환하고 이 Int를 IO 모나드에서 꺼내서 numCases에 바인딩하면 된다. 모든 모나드는 펑터이기 때문에 fmap을 적용할 수 있다. (타입클래스백과를 읽지 않았으면 IO 모나드에 fmap을 적용할 생각은 못 했을 것 같다. 하스켈 입문자라면 강력히 추천하는 글...)

main = do

    num <- fmap read getLine

    print (num*3)


이 상황에 한정하여 세 함수의 타입은 다음과 같고

getLine :: IO String
read :: String -> Int
fmap :: (String -> Int) -> IO String -> IO Int

따라서 num에는 Int 값이 바인딩된다.

사실 이 코드는 엄격한 타입 체계를 중시하는 하스켈에선 조금 엉성한데, print (num*3)을 print num로 바꿔보면 알 수 있다.

main.hs:2:16:

    No instance for (Read a0) arising from a use of `read'

    The type variable `a0' is ambiguous

    Possible fix: add a type signature that fixes these type variable(s)

    Note: there are several potential instances:

      instance Read () -- Defined in `GHC.Read'

      instance (Read a, Read b) => Read (a, b) -- Defined in `GHC.Read'

      instance (Read a, Read b, Read c) => Read (a, b, c)

        -- Defined in `GHC.Read'

      ...plus 25 others

    In the first argument of `fmap', namely `read'

    In a stmt of a 'do' block: a <- (fmap read getLine)

    In the expression:

      do { a <- (fmap read getLine);

           print a }


main.hs:3:5:

    No instance for (Show a0) arising from a use of `print'

    The type variable `a0' is ambiguous

    Possible fix: add a type signature that fixes these type variable(s)

    Note: there are several potential instances:

      instance Show Double -- Defined in `GHC.Float'

      instance Show Float -- Defined in `GHC.Float'

      instance (Integral a, Show a) => Show (GHC.Real.Ratio a)

        -- Defined in `GHC.Real'

      ...plus 26 others

    In a stmt of a 'do' block: print a

    In the expression:

      do { a <- (fmap read getLine);

           print a }

    In an equation for `main':

        main

          = do { a <- (fmap read getLine);

                 print a }


num의 타입이 애매모호하단다. num*3에서 num이 숫자형이라는 걸 추론할 수 있었는데 그냥 print num으로 바꾸니까 num의 타입을 알아낼 수 없는 것이다. 이건 컴파일러가 멍청한 게 아니라 코딩한 사람 잘못이다. 컴파일러가 권고하는 대로 타입을 명확하게 지정해주면 해결된다.

main = do

    num <- fmap (\x -> read x :: Int) getLine

    print (num*3)


람다 함수가 하나 더 추가되어 영 좋지 않다. 더 간단하게는 fmap read getLine 전체의 타입을 명확하게 지정하는 방법이 있다.

main = do

    num <- (fmap read getLine) :: IO Int

    print (num*3)


그리고 이런 것도 된다.

num <- (read `fmap` getLine) :: IO Int    -- 그냥 연산자 버전
num <- (read <$> getLine) :: IO Int    -- fmap과 동일 (※ Control.Applicative 모듈 불러와야 함)

Comments