{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE FlexibleContexts #-}
import Control.Monad
import Control.Monad.State
import Control.Monad.Writer




newtype AppWithNewtype a = AppWithNewtype {
  runAppWithNewtype' :: StateT Int (WriterT [Int] IO) a
} deriving (Monad, MonadIO, MonadState Int, MonadWriter [Int])
-- 이렇게 deriving에 MonadState, MonadWriter을 추가해서, 직접 이 타입에
-- get, put, tell등 MonadState, MonadWriter으로 역할.  이거 없으면,
-- 그냥 lift을 명시적으로 해서 접근해야함.
--
-- SEE: http://stackoverflow.com/questions/18519774/monad-transformer-explicit-lifting
-- SEE: http://stackoverflow.com/a/18520161


appWithNewtypeLiftWriter a = AppWithNewtype $ lift $ a

f :: AppWithNewtype ()
f = do
  cur <- get
  appWithNewtypeLiftWriter $ tell [4, 5]
  appWithNewtypeLiftWriter $ doSomethingToWriter
  liftIO $ putStrLn $ "CUR = " ++ (show cur)
  return ()

-- Writer의 문맥으로만 제한하고 싶은 경우엔 타입을 이렇게 지정.
-- 위의 FlexibleContexts 확장이 필요함.
doSomethingToWriter :: (MonadWriter [Int] m) => m ()
doSomethingToWriter = do
  tell [6, 7]
  return ()
  

--   runApp :: App a -> Int -> IO ((a, Int), [Int])
runAppWithNewtype a init = runWriterT (runStateT (runAppWithNewtype' a) init) 

main :: IO ()    
main = do
  r <- runAppWithNewtype f 123
  putStrLn $ show r
  return ()


-- EOF
