23
20

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Applicative と Monad の違いについて

Last updated at Posted at 2016-01-27

ApplicativeMonadは何が違うのか?」という疑問についての自分なりのまとめ。

型制約の強さの違い

Haskellの型クラスは「特定の名前と型がついた関数が実装されているか」で型をグループ化する概念。言ってみればJavaのinterfaceのようなもので、ある型がその型クラスに属するためにはその型クラスによって指定された名前と型を持つ関数を定義する必要がある。

ApplicativeMonadは型クラスであって、それぞれが要求する関数が異なる。

型クラスの定義を読む

GHCi(7.10.2)によると、Applicativeの定義は以下。

Prelude> :i Applicative
class Functor f => Applicative (f :: * -> *) where
  pure :: a -> f a
  (<*>) :: f (a -> b) -> f a -> f b
  (*>) :: f a -> f b -> f b
  (<*) :: f a -> f b -> f a

(ちなみに小文字で書かれたfabは型変数で、ある具体的な型(例えばIOとかMaybeとか[]とか)が入る。同じ型変数には同じ型が代入されるので、(<*>) :: Maybe (a -> b) -> IO a -> Maybe bのような代入は起こらない。)

(型の定義にHoge a => aと書かれている場合、「aは型クラスHogeに属する」あるいは「aは型クラスHogeのインスタンスである」と読める。Javaでいうところの<T extends Hoge>のようなもの。)

このApplicativeの定義は次のことを言っている。

次の条件を満たすfApplicativeになる:

  • fFunctorに属する
  • a -> f aという型持つpureという関数が実装されている
  • f (a -> b) -> f a -> f bという型を持つ<*>という関数が実装されている
  • f a -> f b -> f bという型を持つ*>という関数が実装されている
  • f a -> f b -> f aという型を持つ<*という関数が実装されている

また、ある型mMonadになるためには、以下で定義された関数を実装しないといけない。

Prelude> :i Monad
class Applicative m => Monad (m :: * -> *) where
  (>>=) :: m a -> (a -> m b) -> m b
  (>>) :: m a -> m b -> m b
  return :: a -> m a
  fail :: String -> m a

このMonadの定義は次のことを言っている。

次の条件を満たすmMonadになる:

  • mApplicativeに属する
  • m a -> (a -> m b) -> m bという型持つ>>=という関数が実装されている
  • m a -> m b -> m bという型を持つ>>という関数が実装されている
  • a -> m aという型を持つreturnという関数が実装されている
  • m aという型を持つfailという関数が実装されている

Monadに属すためには、型クラスApplicativeに含まれる関数も実装しないといけないし、それに加えて型クラスMonadに含まれる関数も実装しないといけない。

型クラスの定義から、「Monadに属する型mは、Applicativeに属する型fよりも強い制約を持っている」という違いがあることがわかった。

「分岐」ができるかどうか

著名なHaskellerであるkazu-yamamotoさんのブログ記事には次のような言及がある。

逐次と反復を実現するのが、Applicative 型クラスです。

分岐...を実現するのが、Monad です。

Applicative にできないことは、前のコンテナの計算結果を用いて、後のコンテナが振る舞いを変えることです。別の言い方をすれば、前のコンテナの計算結果を、後のコンテナに渡す方法がありません。これを実現するのが Monad なのです。

ここでの「分岐」とは「前のコンテナの計算結果を用いて、後のコンテナが振る舞いを変える」のことと思われるが、これは一体どういうことだろう。

>>=の有無による違い

ApplicativeMonadの大きな違いに、>>=の有無がある。
Monad>>=を使えるが、Applicativeは使えない。
これによって、行うことのできる計算の種類に影響がある。

>>=の型を見る

>>=の型宣言は(>>=) :: m a -> (a -> m b) -> m bとなっている。(ここでmMonadに属する。)

引数にa -> m bという関数を取ることができるというところがミソで、「m a」と「m aの実行結果aによって次に実行するm bを選択する関数a -> m b」を>>=で結合すると、m bが得られる。

\l -> if length l > 0 then putStrLn l else putStrLn "(empty)"

例えば、こんなふうに型aの値(ここではl)によって、次に実行される計算(putStrLn lputStrLn "(empty)"か)を切り替えることができる。

これとgetLine>>=で結合して使うことで、getLineを実行した結果によって異なる処理をするIO ()が書ける。

Prelude> getLine >>= (\l -> if length l > 0 then putStrLn l else putStrLn "(empty)")
hoge
hoge
Prelude> getLine >>= (\l -> if length l > 0 then putStrLn l else putStrLn "(empty)")

(empty)

一方、例えばApplicativeの<*>の型宣言は(<*>) :: f (a -> b) -> f a -> f b で、aによってbを変えることはできるけど、f bを変えることはできない。
例えば、

Prelude> (++) <$> getLine <*> getLine
foo
bar
"foobar"

こんな感じでm am bを連結させて逐次実行させることはできるけど、m aの実行結果aに依存してm bを変えることはできない。

結局何が違うの?

型の持つパワーが違う。

Monadが持つ>>=a -> m bという関数を取ることができるため、m aを実行して得られるam bが依存するような処理が書ける。Applicativeにはそれが書けない。

ちなみに、Monadを使っていても前の処理の結果に後ろの処理の実行内容が依存しないのであればApplicativeが提供している関数のみでコードを書いたほうが綺麗で安全。(参考:Applicativeのススメ)

Disclaimer

筆者も勉強中のため内容については間違いがあるかもしれません。
間違いがある場合、コメント欄にてご指摘いただけると助かります。

23
20
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
23
20

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?