Skip to content

Instantly share code, notes, and snippets.

@knzm
Created November 28, 2012 00:40
Show Gist options
  • Save knzm/4158221 to your computer and use it in GitHub Desktop.
Save knzm/4158221 to your computer and use it in GitHub Desktop.
「すごいHaskellたのしく学ぼう!」 11章のまとめ

ファンクターからアプリカティブファンクターへ

Haskell の型の特徴 (おさらい)

  • Haskell では、一般的で抽象的な振る舞いを定義する型クラスを簡単に作ることができる
    • オープンな型クラス
    • 強力な型システム
  • オープンな型システム
    • 型の巨大な階層構造を気にする必要がない
  • 強力な型システム
    • 型宣言から多くの情報を読み取れる

ファンクターとしての IO アクション

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

アプリカティブスタイルを使うと、普通の関数にアプリカティブ値の引数を与えることができる

具体例: Maybe

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 を返す

具体例: List

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 の <*> は、左辺のリストのそれぞれの関数を、右辺のリストのそれぞれの値に適用する

具体例: ZipList

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 型を導入している

具体例: IO

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

liftA2

(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つのアプリカティブ値を引数に取る関数」に昇格させる

sequenceA

(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つのアプリカティブ値」を返す関数

@knzm
Copy link
Author

knzm commented Nov 28, 2012

FunctorとApplicativeとMonadのイメージ図
https://twitter.com/aomoriringo/status/270046177020489728/photo/1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment