- Haskell では、一般的で抽象的な振る舞いを定義する型クラスを簡単に作ることができる
- オープンな型クラス
- 強力な型システム
- オープンな型システム
- 型の巨大な階層構造を気にする必要がない
- 強力な型システム
- 型宣言から多くの情報を読み取れる
instance Functor IO where fmap :: (a -> b) -> IO a -> IO b fmap f action = do result <- action return (f result)
- 関数
f
を IO アクションにfmap
すると、「元の IO アクションの結果に関数f
を適用して返す」 IO アクションが作られる。 - IO アクションという箱の中身に関数
f
を適用できる
instance Functor ((->) r) where fmap :: (a -> b) -> (r -> a) -> (r -> b) fmap f g = (\x -> f (g x))
関数ファンクターの fmap
は関数合成
- 関数
id
でファンクター値を写した場合、ファンクター値が変化しない - すべてのファンクター値 x に対して
fmap (f.g) = fmap f (fmap g x)
が成り立つ
- ファンクター: 箱のようなもの
- アプリカティブファンクター: 関数の入っているファンクター
- アプリカティブ = 適用可能 = 関数のようなもの
- 例)
Just (3*)
とJust 5
からJust 15
を作る- 「ファンクターの中の関数」を「別のファンクターの中の値」に適用させる
- 任意のファンクターで (つまり
fmap
で) これはできない
- アプリカティブファンクターの型宣言
class (Functor f) => Applicative f where pure :: a -> f a (<*>) :: f (a -> b) -> f a -> f b
ghci> import Control.Applicative ghci> pure (+) <*> Just 3 <*> Just 5 Just 8 ghci> fmap (+) (Just 3) <*> Just 5 Just 8 ghci> (+) <$> Just 3 <*> Just 5 Just 8
ここで <$>
は以下のような関数
(<$>) :: (Functor f) => (a -> b) -> f a -> f b f <$> x = fmap f x
アプリカティブスタイルを使うと、普通の関数にアプリカティブ値の引数を与えることができる
instance Applicative Maybe where pure = Just Nothing <*> _ = Nothing (Just f) <*> something = fmap f something
ghci> (+) <$> Just 3 <*> Just 5 Just 8 ghci> (+) <$> Just 3 <*> Nothing Nothing ghci> (+) <$> Nothing <*> Just 5 Nothing
Maybe の <*>
は、左辺が Just
ならその中の値を取り出して右辺を写す。左右どちらかの引数が Nothing
だったら Nothing
を返す
instance Applicative [] where pure x = [x] fs <*> xs = [f x | f <- fs, x <- xs]
ghci> [(+1),(*2)] <*> [3,4] [4,5,6,8] ghci> (*) <$> [2,5,10] <*> [8,10,11] [16,20,22,40,50,55,80,100,110]
List の <*>
は、左辺のリストのそれぞれの関数を、右辺のリストのそれぞれの値に適用する
instance Applicative ZipList where pure x = ZipList (repeat x) ZipList fs <*> ZipList xs = ZipList (zipWith (\f x -> f x) fs xs)
ghci> getZipList $ (,) <$> ZipList [1,2,3] <*> ZipList [4,5,6] [(1,4),(2,5),(3,6)] ghci> getZipList $ (,,) <$> ZipList [1,2,3] <*> ZipList [4,5,6] <*> ZipList [7,8,9] [(1,4,7),(2,5,8),(3,6,9)]
- ZipList はリストに対する別のアプリカティブファンクター
- 同じ型クラスのインスタンスを複数回宣言することはできないので、新たに ZipList 型を導入している
instance Applicative IO where pure = return a <*> b = do f <- a x <- b return (f x)
ghci> let ask prompt = (\x y -> y) <$> putStr prompt <*> getLine ghci> (++) <$> ask "First: " <*> ask "Second: " First: a Second: b "ab"
IO の <*>
は逐次実行
- 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 :: (Applicative f) => (a -> b -> c) -> f a -> f b -> f c liftA2 f a b = f <$> a <*> b
ghci> liftA2 (:) (Just 3) (Just [4]) Just [3,4]
liftA2
は、「通常の2引数関数」を、「2つのアプリカティブ値を引数に取る関数」に昇格させる
(Data.Traversable
で定義)
sequenceA :: (Applicative f) => [f a] -> f [a] sequenceA [] = pure [] sequenceA (x:xs) = liftA2 (:) x (sequenceA xs)
ghci> sequenceA [Just 1, Just 2] Just [1,2] ghci> sequenceA [Just 3, Nothing, Just 1] Nothing ghci> sequenceA [(>4), (<10), odd] 7 [True,True,True] ghci> sequenceA [getLine, getLine, getLine] hello haskell world ["hello","haskell","world"]
sequanceA
は「アプリカティブ値のリスト」を受け取って、「リストを返り値として持つ1つのアプリカティブ値」を返す関数
FunctorとApplicativeとMonadのイメージ図
https://twitter.com/aomoriringo/status/270046177020489728/photo/1