学習の素材は、
すごいHaskellたのしく学ぼう!
この表記は、私見・感想です。
fmapに2引数関数を与える
ファンクターでは、1引数関数を使ってファンクター値を写すことが型のイメージに合っている。この時、2引数関数を使ってファンクター値を写すと、ファンクターの中の値が関数となる。
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を伴う計算を組み合わせることができる。
また、普通の関数をどんなアプリカティブファンクターと組み合わせて使えるので、それぞれの文脈の力を借りることができる。
ただし組み合わせることができるのは同じ文脈での計算に限る。