Haskellではモナドと呼ばれる部品を組み合わせてプログラムを作ります。別種のモナドを組み合わせるためのモナド変換子の使い方の初歩を説明します。ライブラリで用意されたモナド変換子を手っ取り早く使うことを目的としているため、モナド変換子の作り方や圏論には言及しません。
シリーズの記事です。
- Haskell 超入門
- Haskell 代数的データ型 超入門
- Haskell アクション 超入門
- Haskell ラムダ 超入門
- Haskell アクションとラムダ 超入門
- Haskell IOモナド 超入門
- Haskell リストモナド 超入門
- Haskell Maybeモナド 超入門
- Haskell 状態系モナド 超入門
- Haskell モナド変換子 超入門 ← この記事
- Haskell 例外処理 超入門
- Haskell 構文解析 超入門
- 【予定】Haskell 継続モナド 超入門
- 【予定】Haskell 型クラス 超入門
- 【予定】Haskell モナドとゆかいな仲間たち
- 【予定】Haskell Freeモナド 超入門
- 【予定】Haskell Operationalモナド 超入門
- 【予定】Haskell Effモナド 超入門
- 【予定】Haskell アロー 超入門
練習の解答例は別記事に掲載します。
モナド変換子
do
の中では同じ種類のモナドしか使うことができません。
たとえば次のようにStateモナドとIOモナドを併用することはできません。
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
execStateT
やlift
などが新しく登場しています。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
は使えます。
f g x = g x
f g = g
-
f =
← エラー 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モナド変換子のm
にIdentity
を指定したものです。何もしない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以外のモナドを指定すると破綻します。
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モナドのs
にRealWorld
を指定したものと相互変換できる関係と似ています。
-
IO a
⇔ST RealWorld a
IOモナドとSTモナドの関係は等価物として変換可能ということですが、Stateモナドは実体がStateTモナド変換子のため変換は介在しません。
-
State s a
==StateT s Identity a
STは "state-transformer" でモナド変換子のTと同じ単語ですが、STはモナド変換子ではありません。"transformer" の意味合いが異なるようです。
内部関数
s -> m (a, s)
Stateモナドではm
がIdentity
のため、内部関数は次のようになります。
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モナド変換子として作ったst
がrunState
で評価できています。
IOモナドとの比較
内部関数を出し入れする関数をIOモナドと比較します。
型 | 取り出し | 格納 |
---|---|---|
IO | unIO | IO |
StateT | runStateT | StateT |
練習
【問1】Stateモナドを扱うreturn
をStateT
を使って、runState
をrunStateT
を使って再実装してください。
具体的には次のコードが動くようにしてください。
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モナド変換子のm
にIO
を指定すると何が起きるかを確認します。
StateT s IO a
内部関数は次のようになります。
s -> IO (a, s)
これを入れたStateTモナド変換子をrunStateT
で初期値を指定して評価すると、IOモナドが返って来ます。StateTモナド変換子の中にIOモナドが入っていると見なせます。
※ m
にIO
を指定するとStateモナドとは互換性がなくなるためrunState
は使えません。
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
を使用してみます。
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モナド変換子の中に入れています。
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
: モナド → モナド変換子
組み合わせ
先の例のように単独で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モナドに関連した操作はすべて閉じ込められるという原則は守られています。
モナドスタック
モナド変換子とモナドの関係を包含関係として表現しましたが、積み重ねとしても表現できます。後者をモナドスタックと呼びます。包むか積むかという表現方法だけの違いで、意味が変わるわけではありません。
print
はIOアクションですが、StateTモナド変換子を積むことでStateTアクションに変換されます。モナドスタックの上方のアクションに変換される様子を持ち上げと表現します。
練習
【問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のfact
をdo
と<-
で書き直してください。問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 s Maybe a
を評価すればMaybe a
となります。モナドスタックからStateTモナド変換子を取り除いて下のMaybeモナドが残るとイメージできます。
※ ここで取り上げたサンプルに少し手を加えたものを続編の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】次のコードのprint
をmain
から各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
が最上位まで届かずにエラーになります。
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
する必要があります。
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
し過ぎでエラーになります。
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アクション専用です。
先ほどの例を修正します。
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
で解いてください。
⇒ 解答例
関数の持ち上げ
lift
やliftIO
はアクションを持ち上げていましたが、関数の持ち上げもあります。
※ 関数の持ち上げはモナド変換子とは関係ありませんが、持ち上げに関連して取り上げます。
以下、モナドを代表してリストで例を示します。
値とモナドスタック
モナドによる値の包含関係もモナドスタックとして表現することができます。
適用不能
通常の関数はモナドに入っている値に対して適用できません。
main = print $ (+ 1) [1] -- エラー
このようなときに使うのが関数の持ち上げです。
liftM
liftM :: Monad m => (a1 -> r) -> m a1 -> m r
モナドに入っている値に対して、関数を持ち上げることで適用します。
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
を部分適用 →(+) 1
== セクション(1 +)
- 戻り値
(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
です。
これに対して、関数をモナドに入れても関数としては使えません。
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
<*>
の助けが必要です。
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
は以下の記事を参考にしました。
- @fmkz___: fmapとliftMとliftそしてliftIO 2012.7.16
- liftとliftIOの違い - [ pred x | x <- "Ibtlfmm!ojllj" ] - haskell 2009.5.14
謝辞
持ち上げやモナドスタックについては@ruicc先生よりご教示いただきました。
記事としてまとめていただきましたので、理解を深めたい方はご参照ください。
- @ruicc: Haskell - モナドトランスフォーマーとその周辺 - Qiita 2015.2.4