LoginSignup
86
72

More than 3 years have passed since last update.

Haskell モナド変換子 超入門

Last updated at Posted at 2015-01-04

Haskellではモナドと呼ばれる部品を組み合わせてプログラムを作ります。別種のモナドを組み合わせるためのモナド変換子の使い方の初歩を説明します。ライブラリで用意されたモナド変換子を手っ取り早く使うことを目的としているため、モナド変換子の作り方や圏論には言及しません。

シリーズの記事です。

  1. Haskell 超入門
  2. Haskell 代数的データ型 超入門
  3. Haskell アクション 超入門
  4. Haskell ラムダ 超入門
  5. Haskell アクションとラムダ 超入門
  6. Haskell IOモナド 超入門
  7. Haskell リストモナド 超入門
  8. Haskell Maybeモナド 超入門
  9. Haskell 状態系モナド 超入門
  10. Haskell モナド変換子 超入門 ← この記事
  11. Haskell 例外処理 超入門
  12. Haskell 構文解析 超入門
  13. 【予定】Haskell 継続モナド 超入門
  14. 【予定】Haskell 型クラス 超入門
  15. 【予定】Haskell モナドとゆかいな仲間たち
  16. 【予定】Haskell Freeモナド 超入門
  17. 【予定】Haskell Operationalモナド 超入門
  18. 【予定】Haskell Effモナド 超入門
  19. 【予定】Haskell アロー 超入門

練習の解答例は別記事に掲載します。

モナド変換子

doの中では同じ種類のモナドしか使うことができません。

たとえば次のようにStateモナドとIOモナドを併用することはできません。

NG
import Control.Monad
import Control.Monad.State

sum' xs = (`execState` 0) $ do
    forM_ xs $ \i -> do
        modify (+ i)
        v <- get
        putStrLn $ "+" ++ show i ++ " -> " ++ show v  -- エラー

main = do
    print $ sum' [1..5]
エラー内容
Couldn't match type `IO'
              with `StateT c transformers-0.3.0.0:Data.Functor.Identity.Identity'
Expected type: State c ()
  Actual type: IO ()

このような問題に対処するのがモナド変換子です。説明は後回しで例を示します。

import Control.Monad
import Control.Monad.State

sum' xs = (`execStateT` 0) $ do                              -- execStateT
    forM_ xs $ \i -> do
        modify (+ i)
        v <- get
        lift $ putStrLn $ "+" ++ show i ++ " -> " ++ show v  -- lift

main = do
    print =<< sum' [1..5]                                    -- $ → =<<
実行結果
+1 -> 1
+2 -> 3
+3 -> 6
+4 -> 10
+5 -> 15
15

execStateTliftなどが新しく登場しています。printの次が=<<になっています。

これらを説明する前に必要なものから見て行きます。

Identityモナド

型表記
Identity a

中に値が入っているだけの最も単純なモナドです。identityは恒等式という意味で、Identityモナドは恒等モナドとも呼ばれます。

自己同一性などと訳されるアイデンティティと同じ単語です。

値はrunIdentityで取り出せます。

import Control.Monad.Identity

main = do
    let a = return 1 :: Identity Int  -- 生成
    print $ runIdentity a             -- 値の取り出し(評価)
実行結果
1

モナドには値だけが入っていて、関数や状態が隠されてはいません。リストモナドと違って要素数は1固定です。

特別な機能はなく、何もしないモナドです。

id

id :: a -> a

引数をそのまま返す関数です。idはidentityに由来して恒等関数とも呼ばれます。

高階関数に何か関数を渡す際、何もして欲しくないときなどに使います。次の例でのf idが該当します。

f g x = g x

main = do
    print 1
    print $ id 1
    print $ f id 1  -- 注目
実行結果
1
1
1

この例でのfはポイントフリースタイルを進めると引数が全部消えますが、このようなときに右辺を埋めるのにもidは使えます。

  1. f g x = g x
  2. f g = g
  3. f = ← エラー
  4. f = id

Identityはモナド、idは関数での、似たような位置付けのものです。

単位元(掛け算における1)のようなものです。

StateTモナド変換子

Stateモナドに対するモナド変換子です。

  • Tは "transformer" の略で、モナド変換子を表す接尾辞です。
  • モナド変換子はモナドの一種です。

型表記

StateT s m a
  • State s aに対してmが増えています。
  • mは任意のモナドを指定します。モナドの変換に関係します。

Stateモナドとの関係

定義
type State s a = StateT s Identity a

StateモナドはStateTモナド変換子のmIdentityを指定したものです。何もしないIdentityモナドを指定することで、変換子を無効化して単なるモナドにしています。

StateTモナド変換子にIdentityモナドを指定することでStateモナドが作れることを確認します。

import Control.Monad.Identity
import Control.Monad.State

main = do
    let a = return 1 :: StateT s Identity Int  -- StateTとして生成
    print $ evalState a ()                     -- Stateとして評価
実行結果
1

StateTモナド変換子を型指定しているのにevalStateが適用できています。

Identity以外のモナドを指定すると破綻します。

NG
import Control.Monad.Identity
import Control.Monad.State

main = do
    let a = return 1 :: StateT s IO Int
    print $ evalState a ()               -- エラー
エラー内容
Couldn't match type `IO' with `Identity'
Expected type: State () Int
  Actual type: StateT () IO Int

IOを指定するケースがモナド変換子の真骨頂ですが、詳細は後の方で取り上げます。

類似例

IOモナドがSTモナドのsRealWorldを指定したものと相互変換できる関係と似ています。

  • IO aST RealWorld a

IOモナドとSTモナドの関係は等価物として変換可能ということですが、Stateモナドは実体がStateTモナド変換子のため変換は介在しません。

  • State s a == StateT s Identity a

STは "state-transformer" でモナド変換子のTと同じ単語ですが、STはモナド変換子ではありません。"transformer" の意味合いが異なるようです。

内部関数

s -> m (a, s)

StateモナドではmIdentityのため、内部関数は次のようになります。

s -> Identity (a, s)

StateモナドではrunState mとすることで内部関数を取り出せますが、実際に入っている内部関数は戻り値がIdentityモナドで包まれています。実用上はIdentityモナドを見せる必要がないため、runStateは内部関数をラップしてIdentityモナドを隠しています。

runStateT

runStateT :: StateT s m a -> s -> m (a,s)

StateTモナド変換子から内部関数を取り出します。

※ runという表現に違和感があれば、rを無視してunStateTだと見立てると良いかもしれません。

runStateのように関数を加工することはありません。両者を比較します。

import Control.Monad.Identity
import Control.Monad.State

st = return 1 :: State s Int     -- Stateを生成

f1 :: s -> (Int, s)
f1 = runState st                 -- 加工された内部関数を取り出し

f2 :: s -> Identity (Int, s)
f2 = runStateT st                -- 生の内部関数を取り出し

main = do
    print $ f1 ()                -- (値, 状態)
    print $ runIdentity $ f2 ()  -- Identityで包まれている
実行結果
(1,())
(1,())

片方だけを取得する関数

runStateと同じように、runStateTにも値や状態だけを返す関数があります。

  • 値だけ: evalStateT :: (Monad m) => StateT s m a -> s -> m a
  • 状態だけ: execStateT :: (Monad m) => StateT s m a -> s -> m s

比較します。

import Control.Monad.Identity
import Control.Monad.State

main = do
    let st = return 1 :: StateT s Identity Int
    print $ runIdentity $  runStateT st ()      -- (値, 状態)
    print $ runIdentity $ evalStateT st ()      --  値
    print $ runIdentity $ execStateT st ()      --      状態
実行結果
(1,())
1
()

StateT

StateT :: (s -> m (a,s)) -> StateT s m a

関数からStateTモナド変換子を生成します。runStateTの逆です。

応用として、Stateモナドを関数から自作してみます。

import Control.Monad.Identity
import Control.Monad.State

main = do
    let st = StateT $ \s -> Identity (1, s)  -- 関数から生成
    print $ runIdentity $ runStateT st ()    -- StateTとして評価
    print $ runState st ()                   -- Stateとして評価
実行結果
(1,())
(1,())

StateTモナド変換子として作ったstrunStateで評価できています。

IOモナドとの比較

内部関数を出し入れする関数をIOモナドと比較します。

IO-StateT.png

取り出し 格納
IO unIO IO
StateT runStateT StateT

練習

【問1】Stateモナドを扱うreturnStateTを使って、runStaterunStateTを使って再実装してください。

具体的には次のコードが動くようにしてください。

main = do
    let st = return' 1
    print $ runState' st ()
実行結果
(1,())

解答例

持ち上げ

ここからはモナド変換子によって別種のモナドを合成する方法について見て行きます。

サンプルの一部を再掲します。

sum' xs = (`execStateT` 0) $ do
    forM_ xs $ \i -> do
        modify (+ i)
        v <- get
        lift $ putStrLn $ "+" ++ show i ++ " -> " ++ show v  -- 注目

liftの後にIOモナドを記述することで、StateモナドとIOモナドを混ぜています。lift持ち上げと訳されます。

※ ウェイトリフティング(重量挙げ)のリフトです。

まずStateTモナド変換子のmIOを指定すると何が起きるかを確認します。

StateT s IO a

内部関数は次のようになります。

s -> IO (a, s)

これを入れたStateTモナド変換子をrunStateTで初期値を指定して評価すると、IOモナドが返って来ます。StateTモナド変換子の中にIOモナドが入っていると見なせます。

mIOを指定するとStateモナドとは互換性がなくなるためrunStateは使えません。

runStateT.png

import Control.Monad.State

f :: s -> IO (Int, s)         -- 内部関数
f s = return (1, s)

a :: StateT s IO Int
a = StateT f                  -- 関数から生成

main = do
    print =<< f ()            -- 関数を評価
    print =<< runStateT a ()  -- StateTを評価
実行結果
(1,())
(1,())

runStateTは内部関数を取り出すだけです。内部関数を直接評価しても、StateTモナド変換子に入れてrunStateT経由で評価しても、同じ結果です。後者はモナドが介在して追いにくいので、前者の動きに注目すると良いでしょう。

print

内部関数の中でprintを使用してみます。

import Control.Monad.State

f :: s -> IO ((), s)         -- StateTの内部関数
f s = do
    a <- print "hello"       -- printを使用
    return (a, s)            -- IOモナドを返す

a :: StateT s IO ()
a = StateT f                 -- 生成

main = do
    print =<< f 1            -- 関数を評価
    print =<< runStateT a 1  -- StateTを評価
実行結果
"hello"
((),1)
"hello"
((),1)

lift

lift :: (Monad m) => m a -> t m a

モナドからモナド変換子を生成する関数です。型注釈のtはモナド変換子を表します。

内部関数の自作とliftとを比較します。IOモナドをStateTモナド変換子の中に入れています。

lift.png

import Control.Monad.State

a1 :: StateT s IO ()
a1 = StateT $ \s -> do        -- 内部関数の自作
    a <- print "hello"
    return (a, s)

a2 :: StateT s IO ()
a2 = lift $ print "hello"     -- lift

main = do
    print =<< runStateT a1 1  -- 評価結果はIOで返る
    print =<< runStateT a2 1  -- 同上
実行結果
"hello"
((),1)
"hello"
((),1)

捉え方

liftはモナド変換子版のreturnのようなものだと捉えられます。

return-lift.png

  • return: 値 → モナド
  • lift: モナド → モナド変換子

組み合わせ

先の例のように単独でliftを使ってもあまり意味はありませんが、StateアクションとIOアクションとを併用するときに真価を発揮します。

状態をgetで取得してprintで表示する例です。

import Control.Monad.State

a :: StateT String IO ()
a = do
    v <- get        -- Stateアクション
    lift $ print v  -- IOアクション

main = do
    runStateT a "hello"
実行結果
"hello"

runStateTの評価結果はIOモナドに包まれているため、IOモナドに関連した操作はすべて閉じ込められるという原則は守られています。

モナドスタック

モナド変換子とモナドの関係を包含関係として表現しましたが、積み重ねとしても表現できます。後者をモナドスタックと呼びます。包むか積むかという表現方法だけの違いで、意味が変わるわけではありません。

inner-bottom.png

printはIOアクションですが、StateTモナド変換子を積むことでStateTアクションに変換されます。モナドスタックの上方のアクションに変換される様子を持ち上げと表現します。

lifting.png

練習

【問2】StateTモナド変換子を扱うbind, get, modify, liftを実装してください。doは使わないでください。>>=はStateTモナド変換子以外にのみ使ってください。

importの際にオリジナルを隠してください。

import Control.Monad.State hiding (get, modify, lift)

具体的には次のコードが動くようにしてください。

fact x = (`execStateT` 1) $
    forM_ [1..x] $ \i ->
        modify (* i) `bind` \_ ->
        get `bind` \v ->
        lift $ putStrLn $ "*" ++ show i ++ " -> " ++ show v

main = fact 5 >>= print
実行結果
*1 -> 1
*2 -> 2
*3 -> 6
*4 -> 24
*5 -> 120
()

解答例

【問3】問2のfactdo<-で書き直してください。問2で再実装した関数は使わないでください。

解答例

Maybeモナドとの合成

今までの例ではStateTモナド変換子で合成するのはIOモナドばかりでしたが、もちろんそれ以外のモナドとも合成できます。

例としてMaybeモナドを取り上げます。

次の手順でサンプルを書き換える過程を示します。

  • モナドなし
    • Stateモナド
    • Maybeモナド
  • モナド変換子で合成

モナドなし

getchで1文字ずつ取得して、get3で3文字分を返します。文字数が足らないとエラーが発生します。

getch (x:xs) = (x, xs)

get3 xs0 =
    let (x1, xs1) = getch xs0
        (x2, xs2) = getch xs1
        (x3, xs3) = getch xs2
    in [x1, x2, x3]

main = do
    print $ get3 "abcd"  -- OK
    print $ get3 "1234"  -- OK
    print $ get3 "a"     -- NG
実行結果(エラー)
"abc"
"123"
xxx: Main.hs:1:1-22: Non-exhaustive patterns in function getch

Stateモナド

状態の受け渡しが冗長なので、Stateモナドで抽象化します。依然としてエラーは発生します。

import Control.Monad.State

getch = state getch where   -- Stateモナドに関数を入れる
    getch (x:xs) = (x, xs)  -- 状態 -> (値, 状態)

get3 = evalState $ do
    x1 <- getch
    x2 <- getch
    x3 <- getch
    return [x1, x2, x3]

main = do
    print $ get3 "abcd"  -- OK
    print $ get3 "1234"  -- OK
    print $ get3 "a"     -- NG
実行結果
"abc"
"123"
xxx: Pattern match failure in do expression at Main.hs:4:5-10

Maybeモナド

最初のコードに戻って、エラーを避けるためにMaybeモナドで書き換えます。

getch (x:xs) = Just (x, xs)
getch _      = Nothing

get3 xs0 = do
    (x1, xs1) <- getch xs0
    (x2, xs2) <- getch xs1
    (x3, xs3) <- getch xs2
    return [x1, x2, x3]

main = do
    print $ get3 "abcd"  -- OK
    print $ get3 "1234"  -- OK
    print $ get3 "a"     -- NG
実行結果
Just "abc"
Just "123"
Nothing

モナド変換子で合成

StateTモナド変換子でMaybeモナドと合成すれば、StateモナドとMaybeモナドの両方の特徴が使えます。

import Control.Monad.State

getch = StateT getch where
    getch (x:xs) = Just (x, xs)
    getch _      = Nothing

get3 = evalStateT $ do
    x1 <- getch
    x2 <- getch
    x3 <- getch
    return [x1, x2, x3]

main = do
    print $ get3 "abcd"  -- OK
    print $ get3 "1234"  -- OK
    print $ get3 "a"     -- NG
実行結果
Just "abc"
Just "123"
Nothing

getchはMaybeモナドを返す関数をそのままStateTモナド変換子の中に入れています。StateTモナド変換子の中からMaybeモナドが出て来る構造が表現されています。

StateT-Maybe-1.png

型で考えると、StateT s Maybe aを評価すればMaybe aとなります。モナドスタックからStateTモナド変換子を取り除いて下のMaybeモナドが残るとイメージできます。

StateT-Maybe-2.png

※ ここで取り上げたサンプルに少し手を加えたものを続編のHaskell 例外処理 超入門でも説明します。

練習

【問4】次のコードは種類を判別しながら文字列を読み進めることを意図しています。Maybeモナドを使ってエラーが起きないように修正してください。モナド変換子は使わないでください。

import Data.Char

getch f (x:xs) | f x = (x, xs)

test s0 =
    let (ch1, s1) = getch isUpper s0
        (ch2, s2) = getch isLower s1
        (ch3, s3) = getch isDigit s2
    in [ch1, ch2, ch3]

main = do
    print $ test "Aa0"  -- OK
    print $ test "abc"  -- エラー
"Aa0"
Main.hs:3:1-30: Non-exhaustive patterns in function getch

解答例

【問5】問4の解答をStateTモナド変換子を使って書き直してください。

解答例

他のモナド変換子

State以外のモナドにも対応するモナド変換子があります。

モナド モナド変換子 評価関数
Reader r a ReaderT r m a runReaderT
Writer w a WriterT r m a runWriterT
[] a ListT m a runListT

練習

【問6】次のコードのprintmainから各test*doの中に移動してください。実行結果が同じになるように調整してください。

import Control.Monad.Reader
import Control.Monad.Writer
import Control.Monad.List

testR x = (`runReader` x) $ do
    a <- ask
    return $ a + 1

testW x = fst $ runWriter $ do
    tell ""
    return $ x + 1

testL x = do
    [x + 1]

main = do
    print $ testR 0
    print $ testW 0
    print $ testL 0
実行結果
1
1
[1]

解答例

多重持ち上げ

モナドがネストしているとliftが最上位まで届かずにエラーになります。

lift-2-NG.png

NG
import Control.Monad.State
import Control.Monad.Reader

test1 x = (`runStateT` x) $ do  -- StateT
    modify (+ 1)
    a <- get
    lift $ print a              -- OK
    (`runReaderT` a) $ do       -- ReaderT(ネスト)
        b <- ask
        lift $ print $ b + 1    -- エラー

main = do
    test1 1
エラー内容
Couldn't match type `IO' with `StateT b m'
Expected type: StateT b m ()
  Actual type: IO ()

ネストした回数だけliftする必要があります。

lift-2.png

OK
import Control.Monad.State
import Control.Monad.Reader

test1 x = (`runStateT` x) $ do
    modify (+ 1)
    a <- get
    lift $ print a
    (`runReaderT` a) $ do
        b <- ask
        lift $ lift $ print $ b + 1  -- 修正

main = do
    test1 1
実行結果
2
3

その時点でのモナドスタックの最上位まで持ち上げています。

ネストの変動

先ほどの例では直接ネストさせていたのでネスト回数は明白ですが、ネスト対象が分離しているとややこしくなります。

分離して単独で呼ぶとliftし過ぎでエラーになります。

lift-2-NG-2.png

NG
import Control.Monad.State
import Control.Monad.Reader

test1 x = (`runStateT` x) $ do   -- StateT
    modify (+ 1)
    get >>= test2                -- StateTの中でReaderTを使用

test2 x = (`runReaderT` x) $ do  -- ReaderT
    b <- ask
    lift $ lift $ print $ b + 1  -- 2階層目を想定

main = do
    test1 1                      -- OK
    test2 1                      -- エラー(想定階層の相違)
エラー内容
Couldn't match type `t0 IO' with `IO'
Expected type: IO ()
  Actual type: t0 IO ()

liftIO

liftIO :: IO a -> m a

ネストの深さに関係なく最上位まで一気に持ち上げます。IOアクション専用です。

先ほどの例を修正します。

liftIO.png

import Control.Monad.State
import Control.Monad.Reader

test1 x = (`runStateT` x) $ do
    modify (+ 1)
    get >>= test2

test2 x = (`runReaderT` x) $ do
    b <- ask
    liftIO $ print $ b + 1  -- 階層深度の影響を受けない

main = do
    test1 1
    test2 1
実行結果
3
2

test2がネストの深さに関係なく実行できるようになりました。

このようにIOアクションを持ち上げるときはliftIOを使った方が無難です。

練習

【問7】問6をliftIOで解いてください。

解答例

関数の持ち上げ

liftliftIOはアクションを持ち上げていましたが、関数の持ち上げもあります。

※ 関数の持ち上げはモナド変換子とは関係ありませんが、持ち上げに関連して取り上げます。

以下、モナドを代表してリストで例を示します。

値とモナドスタック

モナドによる値の包含関係もモナドスタックとして表現することができます。

value.png

適用不能

通常の関数はモナドに入っている値に対して適用できません。

liftM-NG.png

NG
main = print $ (+ 1) [1]  -- エラー

このようなときに使うのが関数の持ち上げです。

liftM

liftM :: Monad m => (a1 -> r) -> m a1 -> m r 

モナドに入っている値に対して、関数を持ち上げることで適用します。

liftM-OK.png

import Control.Monad

main = print $ liftM (+ 1) [1]
実行結果
[2]

対象となるモナドと同じ高さまで関数を持ち上げています。

liftM2

liftM2 :: Monad m => (a1 -> a2 -> r) -> m a1 -> m a2 -> m r 

引数が2つある関数に対する持ち上げです。

import Control.Monad

main = print $ liftM2 (+) [1] [1]
実行結果
[2]

同様にliftM5まで定義されています。

Applicativeスタイル

関数の持ち上げはHaskell アクション 超入門で取り上げたApplicativeスタイルと同じです。

※ そこでは「値の取り出し」という観点で統一するため、敢えて「持ち上げ」とは言わずに別の説明をしています。結果は同じなので、解釈で何かが変わるわけではありません。

1個目の引数は<$>、2個目以降の引数は<*>でつなぎます。

import Control.Applicative

main = do
    print $ (1 +) <$> [1]
    print $ (+)   <$> [1] <*> [1]
実行結果
[2]
[2]

※ Applicativeを直訳すれば「適用可能」です。「複数の引数が適用可能」というような意味合いです。今回の範囲を超えるため詳細は省略します。

<$>とliftM

<$>liftMの演算子版で、同じ働きです。

交換してみます。

※ 確認が目的です。実用的なコードではありません。

import Control.Applicative
import Control.Monad

main = do
    print $ liftM (1 +) [1]
    print $ (<$>) (1 +) [1]          -- <$>を関数化

    print $ (1 +)   <$>   [1]
    print $ (1 +) `liftM` [1]        -- liftMを演算子化

    print $ (+)   <$>   [1] <*> [1]
    print $ (+) `liftM` [1] <*> [1]  -- liftMと<*>の組み合わせ

    print $ liftM (+) [1] <*> [1]    -- こんなこともできるが
    print $ (<$>) (+) [1] <*> [1]    -- あまり意味はない
実行結果
[2]
[2]
[2]
[2]
[2]
[2]
[2]
[2]

<$>と<*>

今までApplicativeスタイルは使い方しか示しませんでしたが、そもそも<$><*>の違いは何でしょうか。

再実装を通して、違いを探ります。

<$>

<$>を再実装します。

f <$> m = do
    a <- m        -- モナドから値を取り出す
    return $ f a  -- 値を関数に適用してモナドに入れて返す

main = do
    print $ (1 +) <$> [1]
実行結果
[2]

部分適用とモナド

<$>を引数が2つの関数に適用するとどうなるでしょうか。

例: (+) <$> [1]

  1. モナド[1]から値を取り出し → 1
  2. 関数(+)1を部分適用 → (+) 1 == セクション (1 +)
  3. 戻り値(1 +)がモナドに入れて返される → [(1 +)]

引数が足りないため部分適用となり、戻された関数がモナドに入れられます。

<*>

2番目以降の引数はモナドに入った関数に適用します。

<*>を再実装します。

f <$> m = do
    a <- m
    return $ f a

mf <*> m = do
    f <- mf       -- モナドから関数を取り出す
    a <- m        -- モナドから値を取り出す
    return $ f a  -- 値を関数に適用してモナドに入れて返す

main = do
    print $ (+) <$> [1] <*> [1]
実行結果
[2]

<$><*>の違いは、関数がモナドに入っているかどうかです。

<*>だけで書く

最初から関数をモナドに入れれば<*>だけで書けます。

import Control.Applicative

main = do
    print $ return (1 +) <*> [1]
    print $ return   (+) <*> [1] <*> [1]
実行結果
[2]
[2]

値として関数がモナドに入れられるのがポイントです。

第一級関数

値としてモナドに入れられる関数は、状態系モナドの内部関数とは別物です。内部関数の戻り値として生成される対象で、値と同じ扱いです。このように関数が値と同列に扱えることを第一級関数と呼びます。

Stateモナドとリストモナドで例を示します。

import Control.Monad.State

f1 :: State s (Int -> Int)
f1 = return (1 +)                -- 関数をStateモナドに入れる

f2 :: State s (Int -> Int)
f2 = state $ \s -> ((1 +), s)    -- 内部関数が戻すタプルの値が関数

f3 :: [Int -> Int]
f3 = return (1 +)                -- 関数をリストモナドに入れる

main = do
    print $ (evalState f1 ()) 1  -- Stateモナドから取り出した関数を評価
    print $ (evalState f2 ()) 1  -- Stateモナドから取り出した関数を評価
    print $ (f3 !! 0) 1          -- リストモナドから取り出した関数を評価
実行結果
2
2
2

liftMとreturn

return (1 +)で関数をモナドに入れるのと、liftMによる持ち上げとの違いは何でしょうか。

liftMを部分適用すると、モナドを直接評価できる関数が得られます。

import Control.Monad

f = liftM (+ 1)    -- 関数の持ち上げ

main = do
    print $ f [1]  -- モナドを直接評価
実行結果
[2]

このようにモナドを評価できるようにすることが関数の持ち上げです。fの実体は部分適用されたliftMです。

これに対して、関数をモナドに入れても関数としては使えません。

NG
f :: [Int -> Int]
f = return (+ 1)   -- 関数をモナドに入れる

main = do
    print $ f [1]  -- エラー
エラー内容
Couldn't match expected type `[t0] -> a0'
            with actual type `[Int -> Int]'
The function `f' is applied to one argument,
but its type `[Int -> Int]' has none

<*>の助けが必要です。

OK
import Control.Applicative

f :: [Int -> Int]
f = return (+ 1)       -- 関数をモナドに入れる

main = do
    print $ f <*> [1]  -- OK
実行結果
[2]

練習

【問8】次のリスト内包表記をliftM系の関数で書き直してください。

main = do
    print [(x , y) | x <- [0, 1], y <- [0, 2]]
    print [ x + y  | x <- [0, 1], y <- [0, 2]]

解答例

参考

mtlでのStateモナドの定義は途中で変更されたそうです。

liftIOは以下の記事を参考にしました。

謝辞

持ち上げやモナドスタックについては@ruicc先生よりご教示いただきました。

記事としてまとめていただきましたので、理解を深めたい方はご参照ください。

86
72
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
86
72