HaskellのMaybeTモナドトランスフォーマー

モナドトランスフォーマー

モナドトランスフォーマーは、異なる2つのモナドを1つのモナドに合成するために使用されます。モナドトランスフォーマー自体もまたモナドです。

MaybeT

MaybeTモナドトランスフォーマーは、Maybeモナドを他のモナド内にカプセル化することで、両者を統合します。

newtype MaybeT m a = MaybeT { runMaybeT :: m (Maybe a) }
instance (Monad m) => Monad (MaybeT m) where
    return = lift . return
    x >>= f = MaybeT $ do
        v <- runMaybeT x
        case v of
            Nothing -> return Nothing
            Just y  -> runMaybeT (f y)
  • newtype MaybeT m a = MaybeT { runMaybeT :: m (Maybe a) } MaybeT型はnewtypeであり、既存の型をラップしたものです。この型には2つの型パラメータがあります:内部モナドの型mと、基礎となるMaybeモナドのパラメータ型aです。 MaybeT m aはm (Maybe a)型の値をカプセル化し、runMaybeTフィールドを通じてこの値を取り出すことができます。
  • instance (Monad m) => Monad (MaybeT m) where もしmがモナドである場合、MaybeT mもまたモナドです。 モナド型クラスの定義と比較すると、return関数の型シグネチャは: return :: a -> MaybeT m a これはおおよそa -> m (Maybe a)に相当します。 そしてbind演算子の型シグネチャは: >>= :: MaybeT m a -> (a -> MaybeT m b) -> MaybeT m b これはおおよそm (Maybe a) -> (a -> m (Maybe b)) -> m (Maybe b)に相当します。
  • return = lift . return return関数はまず型aの値を内部モナドmにカプセル化し、その後lift関数を通じてMaybeTモナドトランスフォーマーにさらにカプセル化します。 ここで左側のreturnはMaybeTモナドのreturnであり、右側のreturnは内部モナドmのreturnです。
  • x >>= f = MaybeT $ do 関数シグネチャと比較すると、xの型はMaybeT m aであり、これはおおよそm (Maybe a)に相当します。 そしてfの型はa -> MaybeT m bであり、これはおおよそa -> m (Maybe b)に相当します。
  • v <- runMaybeT x xの型と比較すると、vの型はMaybe aであることがわかります。 これはrunMaybeT関数がxをMaybeTモナドから離脱させ、 MaybeT m bです。 したがってf yの型はMaybeT m bであり、runMaybeT (f y)の型はm (Maybe b)です。
Prelude> :m +Control.Monad.Trans.Maybe
Prelude Control.Monad.Trans.Maybe> runMaybeT $ (return 1 :: MaybeT IO Int)
Just 1
Prelude Control.Monad.Trans.Maybe> runMaybeT $ (return 1 :: MaybeT [] Int)
[Just 1]
Prelude Control.Monad.Trans.Maybe> runMaybeT $ (return 1 :: MaybeT IO Int) >>= \x -> return (x * 2)
Just 2
Prelude Control.Monad.Trans.Maybe> runMaybeT $ (return 1 :: MaybeT [] Int) >>= \x -> return (x * 2)
[Just 2]

lift

class MonadTrans t where
    lift :: (Monad m) => m a -> t m a

instance MonadTrans MaybeT where
    lift = MaybeT . liftM Just

lift関数の役割は、既に内部モナド型mにカプセル化された値を、さらにモナドトランスフォーマーt型にカプセル化することです。 MaybeTモナドトランスフォーマーの場合、lift関数の具体的な実装は: まずliftM Justを呼び出して内部モナドm内に埋め込まれた値をMaybeモナドに格納し、 その後MaybeTコンストラクタを呼び出してその値全体をMaybeTモナドに格納します。

Prelude Control.Monad.Trans.Maybe Control.Monad.Trans> runMaybeT $ return 1 >>= lift . print
1
Just ()
Prelude Control.Monad.Trans.Maybe Control.Monad.Trans> runMaybeT $ return 1 >>= \x -> do {lift(print x); return (x * 2)}
1
Just 2
Prelude Control.Monad.Trans.Maybe Control.Monad.Trans> runMaybeT $ return 1 >>= lift . (\x -> [x, x + 1])
[Just 1,Just 2]
MaybeT mがモナド法則を満たすことを証明します。
1. return a >>= f ≡ f a
return a >>= f
≡ (lift . return) a >>= f
≡ lift (m a) >>= f
≡ (MaybeT . liftM Just) (m a) >>= f
≡ MaybeT (m (Just a)) >>= f 
≡ MaybeT $ runMaybeT (f a)
≡ f a
2. m >>= return ≡ m
MaybeT (m (Just x)) >>= return
≡ MaybeT $ runMaybeT (return x)
≡ MaybeT (m (Just x))
MaybeT (m Nothing) >>= return
≡ MaybeT $ (return Nothing)
≡ MaybeT (m Nothing)
3. (m >>= f) >>= g ≡ m >>= (\x -> f x >>= g)
(MaybeT (m (Just x)) >>= f) >>= g ≡ f x >>= g
(MaybeT (m Nothing) >>= f) >>= g ≡ MaybeT (m Nothing) >>= g ≡ MaybeT (m Nothing)
MaybeT (m (Just x) >>= (\x -> f x >>= g) ≡ (\x -> f x >>= g) x ≡ f x >>= g
MaybeT (m Nothing) >>= (\x -> f x >>= g) ≡ MaybeT (m Nothing)

liftIO

class (Monad m) => MonadIO m where
    liftIO :: IO a -> m a

instance MonadIO IO where
    liftIO = id

instance (MonadIO m) => MonadIO (MaybeT m) where
    liftIO = lift . liftIO

liftIO関数の役割は、既にIOモナドにカプセル化された値を、さらにモナドトランスフォーマーm型にカプセル化することです。 (前提として、モナドトランスフォーマーm型は内部にIOモナドをカプセル化している必要があります。) MaybeT IOモナドトランスフォーマーの場合、liftIOはliftと同等の作用を持ちます。

Prelude Control.Monad.Trans.Maybe Control.Monad.Trans> runMaybeT $ return 1 >>= liftIO . print
1
Just ()
Prelude Control.Monad.Trans.Maybe Control.Monad.Trans> runMaybeT $ return 1 >>= \x -> do {liftIO(print x); return (x * 2)}
1
Just 2

liftとliftIOの法則

1. lift . return ≡ return
   liftIO . return ≡ return
2. lift (m >>= f) ≡ lift m >>= (lift . f)
   liftIO (m >>= f) ≡ liftIO m >>= (liftIO . f)
MaybeTにおけるlift関数の定義がliftの法則を満たすことを証明します。
1. lift . return ≡ return
MaybeTにおけるreturn関数の定義から
return ≡ lift . return
2. lift (m >>= f) ≡ lift m >>= (lift . f)
m = n a かつ f a = n bと仮定します
したがってm >>= f = n b
lift (m >>= f)
≡ MaybeT . liftM Just $ m >>= f
≡ MaybeT . liftM Just $ n b
≡ MaybeT (n (Just b))
lift m >>= (lift . f)
≡ (MaybeT . liftM Just $ m) >>= (MaybeT . liftM Just . f)
≡ (MaybeT (n (Just a))) >>= (\x -> MaybeT . liftM Just . f $ x)
≡ MaybeT $ runMaybeT $ MaybeT . liftM Just . f $ a
≡ MaybeT $ runMaybeT $ MaybeT . liftM Just $ n b
≡ MaybeT $ runMaybeT $ MaybeT (n (Just b))
≡ MaybeT (n (Just b))

MaybeTはFunctorでもApplicativeでもある

instance (Functor m) => Functor (MaybeT m) where
    fmap f = mapMaybeT (fmap (fmap f))

mapMaybeT :: (m (Maybe a) -> n (Maybe b)) -> MaybeT m a -> MaybeT n b
mapMaybeT f = MaybeT . f . runMaybeT
    
instance (Functor m, Monad m) => Applicative (MaybeT m) where
    pure = lift . return
    mf <*> mx = MaybeT $ do
        mb_f <- runMaybeT mf
        case mb_f of
            Nothing -> return Nothing
            Just f  -> do
                mb_x <- runMaybeT mx
                case mb_x of
                    Nothing -> return Nothing
                    Just x  -> return (Just (f x))
fmap f x
= mapMaybeT (fmap (fmap f)) x
= MaybeT . (fmap (fmap f)) . runMaybeT $ x
= MaybeT . (fmap (fmap f)) $ m (Maybe a)
= MaybeT (n (Maybe b))
Prelude Control.Monad.Trans.Maybe> runMaybeT $ (*2) <$> (return 1 :: MaybeT IO Int)
Just 2
Prelude Control.Monad.Trans.Maybe> runMaybeT $ (*2) <$> (return 1 :: MaybeT [] Int)
[Just 2]
Prelude Control.Monad.Trans.Maybe> runMaybeT $ (*) <$> (return 1 :: MaybeT IO Int) <*> return 2
Just 2
Prelude Control.Monad.Trans.Maybe> runMaybeT $ (*) <$> (return 1 :: MaybeT [] Int) <*> return 2
[Just 2]

MaybeTはAlternativeでもMonadPlusでもある

instance (Functor m, Monad m) => Alternative (MaybeT m) where
    empty = MaybeT (return Nothing)
    x <|> y = MaybeT $ do
        v <- runMaybeT x
        case v of
            Nothing -> runMaybeT y
            Just _  -> return v

instance (Monad m) => MonadPlus (MaybeT m) where
    mzero = MaybeT (return Nothing)
    mplus x y = MaybeT $ do
        v <- runMaybeT x
        case v of
            Nothing -> runMaybeT y
            Just _  -> return v
Prelude Control.Monad.Trans.Maybe Control.Applicative> runMaybeT $ (empty :: MaybeT IO Int) <|> return 2
Just 2
Prelude Control.Monad.Trans.Maybe Control.Monad> runMaybeT $ (mzero :: MaybeT IO Int) `mplus` return 2
Just 2

実用例

import Control.Monad             (guard)
import Control.Monad.Trans       (lift)
import Control.Monad.Trans.Maybe (MaybeT(..), runMaybeT)

import Data.Char

getUserInfo :: MaybeT IO String
getUserInfo = do
    lift $ putStrLn "お名前を入力してください"
    name <- lift getLine
    guard $ not (null name)
    return name

getAge :: String -> MaybeT IO String
getAge name = do
    lift $ putStrLn ("こんにちは、" ++ name ++ "さん")
    lift $ putStrLn "年齢を入力してください"
    age <- lift getLine
    guard (all isDigit age)
    return age

main :: IO (Maybe String)
main = runMaybeT $ do
    userName <- getUserInfo
    getAge userName
*Main> main
お名前を入力してください
Steven
こんにちは、Stevenさん
年齢を入力してください
20
Just "20"
*Main> main
お名前を入力してください
Steven
こんにちは、Stevenさん
年齢を入力してください

Just ""
*Main> main
お名前を入力してください

Nothing

タグ: Haskell モナドトランスフォーマー MaybeT 関数型プログラミング IOモナド

6月15日 23:07 投稿