타입 결정을 거의 GHC에 떠맡긴 코드입니다.
...
class (MonadIO m) => HasConsole m where
getConsoleCenterChan :: m MessageChan
sendToAllConsoles :: Message -> m ()
= do -- 여기서 IO 작업을 하니, 나중에 m으로 들어올 타입은
sendToAllConsoles msg -- MonadIO 인스턴스여야 합니다.
<- getConsoleCenterChan
ch $ atomically $ writeTChan ch msg
liftIO ...
콘솔에 메시지를 보내는 쓰레드들은 HasConsole
클래스의 인스턴로 만들도록 했습니다.
아래 TrackerM
을 HasConsole
의 인스턴스로 만들었습니다.
...
newtype TrackerM m a = TrackerM { runTrackerM :: ReaderT TrackerConfig (LoggingT m) a }
deriving ( Functor, Applicative, Monad, MonadIO , MonadReader TrackerConfig ) ----------------(A)
runTracker :: (MonadIO m) => TrackerConfig -> TrackerM m a -> m a
= runStdoutLoggingT $ runReaderT (runTrackerM tm) config
runTracker config tm -- TrackerM을 벗기고, ReaderT를 벗기고, LoggingT를 벗기면 m만 남습니다.
-- m이 뭔지는 정해져 있지 않고, MonadIO인스턴스면 OK입니다.
instance (MonadIO m) => HasConsole (TrackerM m) where ----------------------(B)
= asks tcConsoleCenterCha
getConsoleCenterChan -- TrackerM을 HasConsole의 인스턴스로 만들었기 때문에
-- TrackerM 컨텍스트에서 sendToAllConsole을 실행할 수 있습니다.
instance (MonadIO m, MonadLogger m) => MonadLogger (TrackerM m) where ------------------(C)
= TrackerM $ Trans.lift $ do ...
monadLoggerLog a b c d
startTracker :: (MonadIO m, MonadLogger m, MonadReader TrackerConfig m, HasConsole m) => m ()
= do
startTracker <- liftIO $ BS.readFile "example.data"
raw $ convertString $ show $ ...
logInfoN ...
...
sendToAllConsoles ...
main :: IO ()
= do
main ...
withAsync (runTracker trackerCfg startTracker)-- ^^^^^^^^^^^^
-- 컴파일 에러
컴파일 하면 아래 에러가 납니다. MonadLogger
클래스는 IO
인스턴스를 가지고 있지 않다는 에러입니다.
왜 MonadLogger IO
를 찾게 됐을까요?
No instance for (Control.Monad.Logger.MonadLogger IO)
• of ‘startTracker’
arising from a use In the second argument of ‘runTracker’, namely ‘startTracker’
• In the first argument of ‘withAsync’, namely
‘(runTracker trackerCfg startTracker)’
startTracker
는 runTracker
의 인자로 들어가니, TrackerM m
타입으로 추론,runTracker
로 벗겨지면 만날 컨텍스트가 main :: IO ()
이니 TrackerM IO
로 추론,startTracker
는 TrackerM IO ()
타입으로 추론됩니다.startTracker
를 선언할 때, 나중에 들어오는 타입이 따라야만 하는 Constraint를 적어두었습니다. 그럼, 추론된 타입이 startTracker
의 제약사항을 만족하는지 봅니다.
MonadIO (TrackerM IO), -- (A) deriving MonadIO이 있으니 통과
MonadLogger (Tracker IO), -- (C) instance MonadLogger (Tracker M)이 있으니 통과
MonadReader TrackerConfig (TrackerM IO), -- (A) deriving에 MonadReader TrackConfig 있으니 통과
HasConsole (TrackerM IO) -- (B) instance HasConsole (Tracker M) 있으니 통과
위 제약 사항들을 만족하는지 GHC가 확인하게 됩니다. 다 문제 없이 통과했는데, 느닷없이 왜 MonadLogger (Tracker IO)로 추론해서 통과했는데 MonadLogger IO를 찾을까요?
원인은 instance MonadLogger
부분의 제약사항에 있습니다.
instance (MonadIO m, MonadLogger m) => MonadLogger (TrackerM m) where
TrackerM
에 쌓여 있는 m
이 MonadLogger
인스턴스여야 한다는 굳이 필요 없는 제약 조건이 들어가 있었습니다.
startTracker
를 사용use하면서 TrackerM IO
로 추론한 후, MonadLogger (Tracker m)
인스턴스를 살펴보니 TrackerM
에 쌓여 있는 m
, 즉 여기선 IO
가 (MonadLogger m)=>
때문에 MonadLooger
의 인스턴스여야 한다는 결론에 도달합니다.
GHC는 타입이 정해지지 않은 소문자가 보이면