アクションのmはなんのm？

2019/12/3

はじめに

Haskellのモナドについて調べていると、時々「アクション」というものが説明なしに出てくることがありました。コレはなんだろう、普通の関数と違うものなのか？調べていくと恐らく m a だな、という検討はついたのですが、じゃあこの m はただの 型コンストラクタ でもいいのか？などの疑問が沸き起こってきました。

ということで、この m には実は制約があるのか？などについて調べてみました。

...

みたいな疑問が、一ヶ月ぐらい前に沸き起こってまとめてとっさにアドカレに登録したんですが、今見返すと当たり前のことしか言ってないので、なんだか微妙な気持ちなのですが、もしかしたら同じ疑問を持つ入門者の方もいらっしゃるかもしれないので、このまま公開しようと思います。

tl;dr

m a の m はモナド

アクションとはモナド値 m a のこと

追記補足あり

想定読者

モナドについてなんとく知っている

do記法についてなんとなく知っている

アクションのおさらい

code:hs

ac = do

x <- action1 -- ←コレ

y <- action2 -- ←コレ

return (x > y)

このコードに、明示的に型を書くならこんな感じになるでしょうか。

以下のコードは動かないことに注意です。

code:hs

ac :: m c

ac = do

x :: a <- action1 :: m a

y :: b <- action2 :: m b

(return (x > y)) :: m c

ここから以下のようなことが窺い知ることができます。

do式の中では <- を使うことでアクション m a から中身の a を取り出せる

一つのdo式の中で扱えるアクションは m が同じものである必要がある

m a の a のほうの型は異なっていても良い

return では値を再び m で包んだものを返している

do式全体はアクション m c になっている

do式全体の型はdo式の最後の行のアクションの型に一致している

もっとわかりやすく具体的な型を用いて書いてみましょう。

例えば m a を Maybe Int 型として書いてみると、以下のようになります。

code:hs

ac :: Maybe Bool

ac = do

x :: Int <- action1 :: Maybe Int

y :: Float <- action2 :: Maybe Float

(return (x > y)) :: Maybe Bool

このコードのノリで、実際に動くものを書いてみると以下のようになりました。

code:hs

ac :: Maybe Bool

ac = do

x <- Just 2

y <- Just 3.0

return (x > y)

main :: IO ()

main = print ac -- Maybe False

ただの型コンストラクタで試してみる

do式とアクションの扱いはわかったので、さっそく本題の m a の m は何か？を一歩ずつ確認していきます。

最も簡単なものはただの型コンストラクタを自分で定義してみることでしょうか。

こんな型を用意します。

code:hs

data MyMaybe a = MyJust a | MyNothing

Maybe 型風の自作型です。

本来の Maybe 型と異なるのは、なんの型クラスも実装していない点です。

この MyMaybe を使って先程の例のように、do式の中で触ってみます。

code:hs

data MyMaybe a = MyJust a | MyNothing

ac = do

x <- MyJust 2

y <- MyJust 3

return $ x + y

このファイルをghciで読み込んでみると、以下のようなエラーが出ました。

code:err

*Main> :l myMaybe.hs

1 of 1 Compiling Main ( prac.hs, interpreted )

myMaybe.hs:4:5: error:

• No instance for (Monad MyMaybe) arising from a do statement

• In a stmt of a 'do' block: x <- MyJust 2

In the expression:

do x <- MyJust 2

y <- MyJust 3

return $ x + y

In an equation for ‘ac’:

ac

= do x <- MyJust 2

y <- MyJust 3

return $ x + y

|

4 | x <- MyJust 2

| ^^^^^^^^^^^^^

Failed, no modules loaded.

上のエラーの1行目で x <- MyJust 2 に対して、 No instance for (Monad MyMaybe) arising from a do statement と言われています。

これは、 MyMaybe 型はMonad型クラスのインスタンスではないのでdo式の中では使えないぞ、と怒っているのですね。

どうやら、最初の疑問の答えは出たようです。

そう、アクション m a の m はどうやらMonadの m っぽいです。

Monad型クラスのおさらい

ここで、少しMonad型クラスのおさらいをしておきましょう。

型Aをモナドとして扱いたければ、型AをMonad型クラスのインスタンスにする必要があります。

また、Monad型クラスのインスタンスにするためには、Applicative型クラスのインスタンスにする必要があり、

また、そのためにはFunctor型クラスのインスタンスにする必要がありました。

要するに、自作の型Aをモナドにするためには、以下の3つの型クラスのインスタンスに順にしていく必要があります。

1. Functor型クラス

2. Applicative型クラス

3. Monad型クラス

Functor型クラスのインスタンスだけにしてみる

では、話を戻します。

先程、自作のMyMaybe型を作りましたが、これはなんの型クラスのインスタンスでもありませんでした。

そして、do式の中で使おうとするとMonadのインスタンスじゃないから無理やぞ、と怒られました。

すでに答えは見えましたが確認のため、 MyMaybe に Functor 型クラスのみを実装して試してみましょう。

code:hs

data MyMaybe a = MyJust a | MyNothing

-- Functor型クラスのインスタンスにする

instance Functor MyMaybe where

fmap f (MyJust x) = MyJust (f x)

fmap f MyNothing = MyNothing

ac = do

x <- MyJust 2

y <- MyJust 3

return $ x + y

これで、再び試してみます。

すると、先程と全く同じエラーが出ました。

ということで、MyMaybeをMonadの仲間入りさせましょう。

Monadのインスタンスになるためには、Applicativeのインスタンスでもある必要があるので、以下のように実装します。

これは本来の Maybe a 型のときと同じ定義にしています。

code:hs

data MyMaybe a = MyJust a | MyNothing

instance Functor MyMaybe where

fmap f (MyJust x) = MyJust (f x)

fmap f MyNothing = MyNothing

instance Applicative MyMaybe where

pure = MyJust

MyNothing <*> _ = MyNothing

(MyJust f) <*> something = fmap f something

instance Monad MyMaybe where

return x = MyJust x

MyNothing >>= f = MyNothing

MyJust x >>= f = f x

ac = do

x <- MyJust 2

y <- MyJust 3

return $ x + y

これを読み込んでみると、予想通り、エラーが出ませんでした。

アクションとは

これで、アクション m a の m はMonadの m であることがわかりましたが、実際問題「アクションの m はMonadの m だよ」ってどこかに定義されているのでしょうか。

つまり、ghciで :t して、Monadの型制約があるよ、というのを確認したいのです。

というのも、 mrsekut.icon がモナドについて調べてる時、急にdo式の話をされ、さぞ知っているのが当たり前かのように「アクション」という単語が出てきました。

mrsekut.icon はこの「アクション」と「関数」の違いがわからず、「？？？」ってなったので、とにかく信頼元の定義が知りたいのです。

そこで do の型を調べればよいのでは？と思い試してみました。

code:shell

Prelude> :t do

<interactive>:1:1: error: Empty 'do' block

Oh...

doは関数じゃなかった。

doとbindの関係性のおさらい

途方に暮れかけていましたが、ここで「do式はbindの糖衣構文」というのを思い出します。

bindとは (>>=) のことです。

さきほど、Monad型クラスのインスタンスにする時に出てきましたね。再掲します。

code:hs

instance Monad MyMaybe where

return x = MyJust x

MyNothing >>= f = MyNothing -- ←こいつ

MyJust x >>= f = f x -- ←こいつ

bindの型を確認してみましょう。

code:shell

Prelude> :t (>>=)

(>>=) :: Monad m => m a -> (a -> m b) -> m b

モナド値 m a と

普通の値を引数にとって、モナド値を返す関数 a -> m a

の2つを引数にとって、モナド値 m b を返すんですね

do式とbindがどんな風に糖衣構文なのかを確認します。

これと、

code:hs

a x = do

y <- Just x

Just y

これは、同じです。

code:hs

a' x = Just x >>= Just

1行前で取り出した値を次の行で、再びJustで括って返しています。

1行前でなくても頑張ればいけます。

例えば、これと

code:hs

a = do

x <- Just 4

y <- Just 10

return $ x + y

これは、同じになります。

code:hs

aa' = Just 4 >>= (\x -> Just 10 >>= (\y -> return (x + y)))

bindの方は、ラムダ式も入ってきてパッと見、わかりにくいですね。

それもそのはずで、本来はbindだけでも全く同じことができていましたが、より見やすく、考えやすく扱うために糖衣構文であるdoがあるのです。

アクションとは、再び

脱線しましたが、話を戻します。

何がしたかったかというと、アクションってなんだよ、の明確な解答が欲しかったのです。

do式の中の右辺で使っているやつがアクションであり、糖衣構文なのでそれはbindの第一引数になるということを確認しました。

つまり、アクションとは、「bindの第一引数として取れるやつ」ということになります。

なので結局 Monad m => m a になるわけですね。

この記事中で雑に「モナド値」と呼んでたそれがアクションのようです。

じゃあ、 Monad m => a -> m a のような関数があったら、これはなんと呼ぶのでしょうか。

「アクションを返す関数」です。

微妙に残る疑問点

mrsekut.icon は記事の流れそのままでアクションについて考えていたのですが、結局「アクションとはこういうものだよ」という公式の文献に当たれていません。

識者にお聞きしたいのは以下の点です。

「アクション」という言葉は公式に定義されているのか

ちょっとググると「IOアクション」が沢山出てくるが、これはなぜか

なぜIOモナドの値のみが特別扱いされているのでしょうか。最初「アクション」とはIOアクションだけに関する用語なのかなと誤認をしましたが、慣習なのでしょうか。

追記

詳しくは、このツイートを見ていただけるとわかりやすいかと思いますが、個人的なメモを下に残しておきます。

Haskell Language Reportの7章 の冒頭にactionについての言及がある

広義の意味では、「アクション」は逐次的に実行するもの

Haskellでは、式の評価順序は定められていない

しかし、副作用を扱うIOでは、評価の順序が結果に作用するので、順序を規定する必要がある

評価順序の決まっていないHaskell界の「関数」と、評価順序の決まっているソレを区別するために、後者を「アクション」と呼んでいる

なので、「アクション == モナド値」というのは若干微妙だが、文脈に依存する機構であるモナドの中で扱うものなので、部分的には正しい、と思う

しかし、例外もあるようで、モナドでない箇所でも逐次処理が必要な場合、それを「アクション」と呼ぶこともあるらしい

え、じゃあ m a の m はモナドじゃないこともあるのか.. mrsekut.icon

たまに「モナドとは純粋関数の世界で副作用を扱うもの」という説明を見かけることがある気がするが、全てのモナドが副作用を扱うわけではない

副作用を扱うもので顕著なのがIOモナド

なので、「IOアクション」がよく目につく感じになっている

結論

以上を踏まえたまとめです。

アクション m a の m は(多くの場合?)モナドの m です。

また、アクションとは、順序の規定された関数のようなもの、ということになります。

結論があやふやな記事になってすみません、何かのお役に立てれたら幸いです。