LoginSignup
9
6

More than 5 years have passed since last update.

アプリカティブファンクターについて勉強したことの整理

Last updated at Posted at 2015-03-08

学習の素材は、
すごいHaskellたのしく学ぼう!

この表記は、私見・感想です。

fmapに2引数関数を与える

ファンクターでは、1引数関数を使ってファンクター値を写すことが型のイメージに合っている。この時、2引数関数を使ってファンクター値を写すと、ファンクターの中の値が関数となる

二項演算子(+)をMaybeに適用する
Prelude Control.Applicative> :t fmap (+) (Just 5)
fmap (+) (Just 5) :: Num a => Maybe (a -> a)

この中の関数を取り出し、何かの値に適用するには、関数を取る1引数関数をfmap すれば良い。例えば次のようなケースが考えられる。

fmap (\f -> f 8) (fmap (+) (Just 5)) -- Just 13

ファンクター値( 中身は関数 )を、他のファンクター値に適用するには

では、この中の関数を取り出し、同じファンクターの任意の値に適用するにはどうしたらよいか。普通のファンクターではこれは無理なので アプリカティブファンクターを用いることになる。

アプリカティブファンクターの定義
class Functor f => Applicative f where
    -- | Lift a value.
    pure :: a -> f a

    -- | Sequential application.
    (<*>) :: f (a -> b) -> f a -> f b
関数・演算子 処理内容
pure pure :: Applicative f => a -> f a 任意の型の引数を受け取り、それをアプリカティブ値の中に入れて返す
<*> (<*>) :: Applicative f => f (a -> b) -> f a -> f b fmap の強化版。普通の関数ではなく、ファンクター値の中にある関数を引数に取る
<$> (<$>) :: Functor f => (a -> b) -> f a -> f b fmap f x と等価な演算子

ということで、上の問いは、次のように解くことができる。

Prelude Control.Applicative> (fmap (+) (Just 5)) <*> (Just 8)
Just 13

なお、pure<$>を用いて次のように書くことができる( アプリカティブ則のひとつ )。

pure (+) <*> (Just 5) <*> (Just 8) -- pureと最初の<*>の結果、Just (+ 5) が返る。続けざまに、中の関数を次のアプリカティブ値に適用している
-- or
(+) <$> (Just 5) <*> (Just 8)

<*> を続けざまに用いることを、アプリカティブ・スタイル と呼ぶ。

アプリカティブ・スタイルのイメージ
f <$> x <*> y <*> z

アプリカティブファンクターであるリスト

リストもアプリカティブファンクターなので、インスタンス宣言が次のように書かれている。

instance Applicative [] where
    pure x = [x]
    fs <*> xs = [f x | f <- fs, x -< xs]

<*> はリスト内包表記で定義されている。左辺のリストのそれぞれの関数を、右辺のリストのそれぞれの値に適用するので、結果としては左辺と右辺のすべての組み合わせが計算されたものがリストに入ることになる。

Prelude Control.Applicative> (+) <$> [1,2,3] <*> [4, 5, 6]
[5,6,7,6,7,8,7,8,9]

リストは非決定性計算とみなすことができる。100や"what" のような値は答えが一つしかない決定性計算であるのに対し、[1, 2, 3] のようなリストは、「どの答えがいいか決められないので可能性のある答えを全て提示している」と見なせる。この見方に立つと、上のコードは、二つの非決定性計算を+で足していて、一層自身のない新たな非決定性計算の結果が生じた、と解釈できる。

アプリカティブ・スタイルを用いると、[x * y | x <- [2, 5, 10], y <- [8, 10, 11] ] のようなリスト内包表記を次のようによりきれいに書くことができる。

(*) <$> [2, 5, 10] <*> [8, 10, 11] --[16,20,22,40,50,55,80,100,110]

TODO: 非決定性について整理する

IOもアプリカティブファンクター

IOもアプリカティブファンクターのインスタンスとして定義されている。

instance Applicative IO where
    pure = return
    <*> = (Functor f) => f(a -> b) -> f a -> f b

たとえば、ユーザに入力を2回求め、その結果の文字列を連結させるには、

(++) <$> getLine <*> getLine

と書けば良い。これは、do記法を用いると

main = do
    a <- getLine
    b <- getLine
    print a ++ b

と等価となる。

[IO a]型の値をIO [a]型の値に変えたいと思ったら、まずすべてのIOを直結し、表示のタイミングで評価が強制され次第、IOを一つずつ実施するしかない。

必要なタイミングでIOアクションが実行され( 遅延IO )、結果を取り出していくことになる。

なお、インスタンスの定義は次の形になっていて、liftM2 idと等価である。

liftM2  :: (Monad m) => (a1 -> a2 -> r) -> m a1 -> m a2 -> m r
liftM2 f m1 m2          = do { x1 <- m1; x2 <- m2; return (f x1 x2) }

ap = liftM2 id

instance Applicative IO where
    pure = return
    (<*>) = ap -- liftM2 id なので実質 左辺を右辺に適用し、アプリカティブで包む処理を行っている

アプリカティブ・スタイル

まとめとして、<$><*> を使うだけで、非決定性計算、失敗するかもしれない計算、IOを伴う計算を組み合わせることができる。
また、普通の関数をどんなアプリカティブファンクターと組み合わせて使えるので、それぞれの文脈の力を借りることができる。

ただし組み合わせることができるのは同じ文脈での計算に限る。

9
6
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
9
6