

monad-
controlパッケージにはMonadTransControlとMonadBaseControlがありますが、どのような使い分けをするのが正しいのでしょうか？  
<http://maoe.hatenadiary.jp/entry/20111207/1323185162>  
には

> 簡単に解説すると、MonadTransControlクラスのliftWith :: Monad m => (Run t -> m a) ->  
>  t m aはモナドスタックt m aを一つpopした型m  
>  aを弄れるようにする役割を持ち、MonadBaseControlのliftBaseWith :: (RunInBase m b -> b  
>  a) -> m aは、スタックの最下部までpopしたベースモナドの型b aを弄れるようにする役割を持っています。

とかいてありますが、どちらも一番下まで持ち上げなければなりませんでした。  
MonadTransControlはMonadBaseControlを定義するための補助なのでしょうか？

[code]

    {-# LANGUAGE ScopedTypeVariables #-}
    {-# LANGUAGE DeriveDataTypeable #-}
    {-# LANGUAGE FlexibleContexts #-}
    {-# LANGUAGE FlexibleInstances #-}
    {-# LANGUAGE GeneralizedNewtypeDeriving #-}
    {-# LANGUAGE MultiParamTypeClasses #-}
    {-# LANGUAGE StandaloneDeriving #-}
    {-# LANGUAGE TypeFamilies #-}
    {-# LANGUAGE UndecidableInstances #-}
    
    import Control.Monad.Base
    import Control.Monad.Reader
    import Control.Monad.State
    import Control.Applicative
    import Control.Monad.Trans.Control
    import Control.Monad.Identity
    import Control.Monad.Except
    import Control.Monad.Trans.Except
    import Data.Maybe
    import qualified Data.Map as Map
    
    type Name  = String
    type Value = String
    type Env   = Map.Map Name Value
    -- EvalRの内部
    newtype EvalE m a = EvalE {runEvalE :: ExceptT String m a}
      deriving (Functor
              , Applicative
              , Monad
              , MonadError String
              , MonadBase base
              )
    instance MonadTrans EvalE where
      lift = EvalE . lift
    
    instance MonadTransControl EvalE where
      type StT EvalE a = StT (ExceptT String) a
      liftWith = defaultLiftWith EvalE runEvalE
      restoreT = defaultRestoreT EvalE
    
    instance (MonadBaseControl b m) => MonadBaseControl b (EvalE m) where
      type StM (EvalE m) a = ComposeSt EvalE m a
      liftBaseWith     = defaultLiftBaseWith
      restoreM         = defaultRestoreM
    evalET :: (Monad (t []), MonadTransControl t) => t [] Int
    evalET = return 1
    evalEM :: (MonadBaseControl [] m) => m Int
    evalEM = return 1
    
    doEvalET :: EvalE [] Int -> EvalE [] Int
    doEvalET t = liftWith (\run -> run t) >>= restoreT . return
    doEvalEM :: EvalE [] Int -> EvalE [] Int
    doEvalEM m = liftBaseWith (\run -> run m) >>= restoreM
    
    exEvalE  :: EvalE [] Int -> IO ()
    exEvalE  = print . runEvalE
    exEvalET :: EvalE [] Int -> IO ()
    exEvalET = print . runEvalE . doEvalET
    exEvalEM :: EvalE [] Int -> IO ()
    exEvalEM = print . runEvalE . doEvalEM
    
    newtype EvalR m a = EvalR {runEvalR :: ReaderT Env (EvalE m) a}
      deriving (Functor
              , Applicative
              , Monad
              , MonadError String
              , MonadReader Env
              , MonadBase base
              )
    instance MonadTrans EvalR where
      lift = EvalR . lift . lift
    
    -- 全部持ち上げないとだめだった
    -- 本当は1つだけ持ち上げてEvalEにしかかった
    instance MonadTransControl EvalR where
      type StT EvalR a = StT EvalE (StT (ReaderT Env) a)
      liftWith f = EvalR $ liftWith $ \runReader ->
                              liftWith $ \runEval ->
                                f $ runEval . runReader . runEvalR
      restoreT = EvalR . restoreT . restoreT
    
    instance (MonadBaseControl b m) => MonadBaseControl b (EvalR m) where
        type StM (EvalR m) a = ComposeSt EvalR m a
        liftBaseWith     = defaultLiftBaseWith
        restoreM         = defaultRestoreM
    
    env :: Env
    env = Map.fromList [("0", "zero"), ("1", "one")]
    
    doEvalRT :: EvalR [] Int -> EvalR [] Int
    doEvalRT t = do
        x <- liftWith $ \r ->
          r t
        restoreT $ return x
    doEvalRM :: EvalR [] Int -> EvalR [] Int
    doEvalRM m = liftBaseWith (\run -> run m) >>= restoreM
    
    exEvalR  :: EvalR [] Int -> IO ()
    exEvalR  = print . runEvalE . (flip runReaderT env) . runEvalR
    exEvalRT :: EvalR [] Int -> IO ()
    exEvalRT = print . runEvalE . (flip runReaderT env) . runEvalR . doEvalRT
    exEvalRM :: EvalR [] Int -> IO ()
    exEvalRM = print . runEvalE . (flip runReaderT env) . runEvalR . doEvalRM
    
[/code]

それとも型がt m aとm aで型が違うのでうまく使い分けるのでしょうか？

[code]

    maybeTIO :: (Monad (t IO), MonadTransControl t) => t IO Int
    maybeTIO = lift     (putStrLn "testMaybeT") >> return 1
    maybeMIO :: (MonadBaseControl IO m) => m Int
    maybeMIO = liftBase (putStrLn "testMaybeT") >> return 1
    
    twiceIO :: IO a -> IO a
    twiceIO action = action >> action
    
    testLiftWith     :: MaybeT IO Int -> MaybeT IO Int
    testLiftWith     f = liftWith     (\run -> twiceIO (run f)) >>= restoreT . return
    testLiftBaseWith :: MaybeT IO Int -> MaybeT IO Int
    testLiftBaseWith f = liftBaseWith (\run -> twiceIO (run f)) >>= restoreM
    testControl      :: MaybeT IO Int -> MaybeT IO Int
    testControl      f = control (\run -> twiceIO (run maybeTIO))
    
[/code]


CC BY-SA 3.0
2015-03-27T09:28:11.337
8486
2015-03-30T06:37:30.683
9043
question
haskell
monad-controlのMonadTransControlとMonadBaseControlについて


提示された例では、`MaybeT
IO`という二階層のモナドスタックが使用されているため、`MonadTransControl`を使っても`MonadBaseControl`を使っても持ち上げられ方は変わりませんが、三階層以上のスタックを使うと違いが明確になります。

以下の例では、`StateT Int (MaybeT
IO)`という三階層のスタックを使っています。この場合、`MonadBaseControl`を使用した`testLiftBaseWith2`は自動的に二階層持ち上げてくれるので、`testLiftBaseWith`のコードがそのまま使えます。一方、`MonadTransControl`を使用した`testLiftWith2`では、一つの`liftWith`が一階層しか持ち上げないため、明示的に二回持ち上げる必要があります。

[code]

    testLiftWith2 :: StateT Int (MaybeT IO) Int -> StateT Int (MaybeT IO) Int
    testLiftWith2 f = liftWith (\runState -> liftWith (\runMaybe -> twiceIO (runMaybe (runState f))) >>= restoreT . return) >>= restoreT . return
    
    testLiftBaseWith2 :: StateT Int (MaybeT IO) Int -> StateT Int (MaybeT IO) Int
    testLiftBaseWith2 f = liftBaseWith (\run -> twiceIO (run f)) >>= restoreM
    
[/code]

`MonadTrans`の`lift`が一階層しか持ち上げないのに対して、`MonadIO`の`liftIO`が最下層の`IO`を何階層でも持ち上げられるのと同様に考えれば良いと思います（`MonadBase`は`MonadIO`をSTモナドにも適用できるように一般化したものです）。


CC BY-SA 3.0
2015-03-30T04:34:59.580
8548
2015-03-30T06:37:30.683
2015-03-30T06:37:30.683
3066
3066
8486
answer