Graphics Programming
IO 모나드와 Functor 본문
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 모듈 불러와야 함)