第10章は勉強したことを実際に使ってプログラムを書く章であった。実用はともかく新しい概念を勉強したいのでまとめることは時間の関係でパスすることにする。第11章はファンクタ、アプリカティブファンクタである。
ファンクタ、アプリカティブ、モナドについてはHaskell - 箱で考えるFunctor、ApplicativeそしてMonad - Qiitaが分かりやすい。
ファンクタ
ここでファンクタの定義を書いておく
-- 定義 class Functor f where fmap :: (a -> b) -> f a -> f b -- ファンクタのインスタンスの定義 instance Functor Tree where fmap f EmptyTree = EmptyTree fmap f (Node x left right) = Node (f x) (fmap f left) (fmap f right)
純粋な空間から出て実世界の値をやりとりするIOアクションもファンクタである。そこでfmapを使ってIOアクションの中身にアクセスできる。
instance Functor IO where fmap f action = do result <- action return $ f result main = do line <- getLine putStrLn $ reverse line -- 等価 main = do line <- fmap reverse getLine putStrLn line
ファンクタの簡単な喩えとして箱があるが、値を修飾するものと捉えることも出来る。その例が関数である。関数の定義に用いる記号->
は
instance Functor ((->) r) where fmap f g = (\x -> f (g x))
と定義される。これをfmapの定義に代入して見ると
fmap :: (a -> b) -> ((->) r a) -> ((->) r b) -- 等価 fmap :: (a -> b) -> (r -> a) -> (r -> b)
これはInt -> Charの関数をとって、[Int]をとって[Char]をとるmap関数とそう変わらないし、ファンクタとしてひとくくりにする条件がfmapが定義できることにしても良さそう。fmapは(a -> b)をとって、(f a -> f b)を返すと見ることもでき、この操作を関数の持ち上げという。
fmapは2引数関数で写すことも出来る。つまり普通の関数をファンクタの中に適用できる。
> :t (++) (++) :: [a] -> [a] -> [a] > :t fmap (++) (Just "hey") fmap (++) (Just "hey") :: Maybe ([Char] -> [Char]) > fmap ($9) $ fmap (*) [1, 2, 3]
ファンクタ則
ファンクタはfmapが定義できたら良さそうだと書いたが、実際にはファンクタになるには以下の2つの法則を満たさないといけない。Haskellはファンクタ則を満たすかどうかのチェックはできないので自分で確認する必要があることに注意。
- 第1法則
fmap id := id
- 第2法則
fmap (f . g) x = fmap f (fmap g x)
これらの法則を満たさない例としてMaybeもどき、
data CMaybe a = CNothing | CJust Int a deriving (Show) instance Functor CMaybe where fmap f CNothing = CNothing fmap f (CJust counter x) = CJust (counter + 1) (f x)
がある。これはfmapを繰り返し適用すると、次第にIntの部分が大きくなっていってしまう。一見fmapの定義はできていて、コンパイルも通るのだが第1法則、第2法則のどちらも満たさない。
アプリカティブ(or アプリカティブファンクタ)
アプリカティブはファンクタと違ってファンクタの中の関数をファンクタの中の値に適用できる。Control.Applicativeをimportして使う。
アプリカティブの定義:
class (Functor f) => Applicative f where pure :: a -> f a (<*>) :: f (a -> b) -> f a -> f b
pure
は文脈(型推論?)で指定されるアプリカティブの値にして返す。<*>
はfmap :: (a -> b) -> f a -> f bに似ている。<*>(と後述の<$>)は左結合。
Maybe
以下ではMaybeやIO、リスト、関数などファンクタの性質があったものが実はアプリカティブでもあることを見ていく。
Maybeはファンクタであると同時にアプリカティブファンクタでもある。アプリカティブファンクタの定義から分かることとして、型コンストラクタfも具体型を1つ型引数にとることがわかるのでむやみに(Maybe a)として具体型にしてはいけない。
-- 間違い!!! -- instance Applicative (Maybe a) where instance Applicative Maybe where pure = Just Nothing <*> _ = Nothing (Just f) <*> something = fmap f something
これを使うと、(<$>
はfmapと等価な中置関数、f <$> x = fmap f x。fmapと等価なのでアプリカティブでなくてもファンクタであれば適用できる。)
-- 等価 > Just (+3) <*> Just 5 > pure (+3) <*> Just 5 > fmap (+3) Just 5 > (+3) <$> Just 5 8 > Nothing <*> Just 4 Nothing
リスト
instance Applicative [] where pure x = [x] fs <*> xs = [f x|f <- fs, x <- xs]
Maybeの最小の「文脈」はNothingで要素を格納するためpure = Justとした。リストの場合、最小の文脈は[]で、要素を格納すれば[x]とすればいい。<*>
については左辺のリストの関数を右辺との全ての組み合わせで格納する。リストを複数の要素を持つ非決定計算とみなすと、<*>
は非決定性計算を組み合わせてより大きい非決定計算を作る演算と理解できる。
> [(+), (*)] <*> [1, 2] <*> [3, 4] [4, 5, 5, 6, 3, 4, 6, 8]
Zipリスト
リストの<*>
の定義を書き換えたものがZipList。
instance Applicative ZipList where pure x = ZipList (repeat x) ZipList fs <*> ZipList xs = ZipList (zipWith (\f x -> f x) fs xs)
例: (ZipListはShowのインスタンスではない)
> getZipList $ (+) <$> ZipList [1, 2, 3] <*> ZipList [100, 100, 100] > getZipList $ (+) <$> ZipList [1, 2, 3] <*> pure 100 [100, 200, 300] > getZipList $ max <$> ZipList [1, 2, 3] <*> ZipList [10, 10, 10, 10] [10, 10, 10] -- (,,)は\x y x -> (x, y, z)と等価 > getZipList $ (,,) <$> ZipList "dog" <*> ZipList "cat" <*> ZipList "rat" [('d','c','r'), ('o','a','a'), ('g','t','t')]
IO
instance Applicative IO where pure = return a <*> b = do f <- a x <- b return (f x)
例:
-- 等価 myAction :: IO String myAction = do a <- getLine b <- getLine return $ a ++ b myAction :: IO String myAction = (++) <$> getLine <*> getLine
関数
関数をアプリカティブとして使うことは少ない。
instance Appicative ((->) r) where pure x = (\_ -> x) f <*> g = \x -> f x (g x)
例:
> pure 3 [1, 3] > pure 3 "blah" 3 -- (+3)と(*100)に引数を与えて結果を足す関数 > (+) <$> (+3) <*> (*100) $ 5 508
アプリカティブ則
pure f <*> x = fmap f x
pure id <*> v = v
pure (.) <*> u <*> v <*> w = u <*> (v <*> w)
pure f <*> pure x = pure (f x)
u <*> pure y = pure ($ y) <*> u
アプリカティブを使ってみる
Control.Applicativeの関数にliftA2がある。
liftA2 :: (Applicative f) => (a -> b -> c) -> f a -> f b -> f c liftA2 f a b = f <$> a <*> b
これは単にアプリカティブスタイルを関数にしただけだが、普通の2引数関数を2つのアプリカティブ値を引数に取る関数に昇格させる関数と見ることが出来る。
以下具体例
-- 等価 sequenceA :: (Applicative f) => [f a] -> f [a] sequenceA [] = pure [] sequenceA (x:xs) = (:) <$> x <*> sequenceA xs sequenceA = foldr (liftA2 (:)) (pure [])
このsequenceAはアプリカティブ値のリストをとって、リストを返り値に持つアプリカティブを返す。例えば、
> sequenceA [Just 1, Just2] Just [1, 2] > sequenceA [(+1), (*3), (-1)] 3 [4, 9, 2] -- 非決定性計算 > sequenceA [[1, 2], [3, 4]] > [[x,y] | x <- [1, 2], y <- [3, 4]] [[1, 3], [1, 4], [2, 3], [2, 4]] > sequenceA [getLine, getLine, getLine] a b c ["a", "b", "c"]