すごいHaskell たのしく学ぼう 読解3
すごいHaskell延長戦です。
あまりHaskell自体を使う気はないので、概念だけ読み取って実際的な話は飛ばす。
- 作者: Miran Lipovaca
- 出版社/メーカー: オーム社
- 発売日: 2012/09/21
- メディア: Kindle版
- 購入: 4人 クリック: 9回
- この商品を含むブログを見る
第11章 ファンクターからアプリカティブファンクターへ
前半部分は第7章のファンクターに関する復習になりますので飛ばしました。
関数の持ち上げ(lifting)
Play frameworkやScalatraと並んで開発されていたScalaのWEBフレームワークにLiftがありましたが、まあこれが名前の元ネタでしょう。fmapの構造は以下のとおりでしたが
fmap :: ( a -> b ) -> f a -> f b
自然言語による説明を付記します
どうやらfmapは、「ある型aから別の型bへの関数」と、「ある型aに適用されたファンクター値」を取り、「別の型bのほうに適用されたファンクター値」を返す関数のようです。
これは以下のようにも捉えられます
fmap :: (a -> b) -> (f a -> f b)
関数を取って「元の関数に似ているけどファンクター値を取ってファンクター値を返す関数」を返す関数だと思うこともできます。
fmapに対する結論
・fmapは関数とファンクター値を取って、その関数でファンクター値を写して返すものである
・fmapは値から値への関数を取って、それをファンクター値からファンクター値への関数に持ち上げたものを返す関数である
この2つは両立するというか、ひとつの物事を別の側面から見たということになります。
個人的にはliftingのほうが脳内でエミュレートしやすいです。なんせ、こちらは関数がネストしてないですから。
11.2 ファンクター則
これは先のエントリですでに述べました。nantonaku-shiawase.hatenablog.com
・第一法則 → 恒等射の保存
・第二法則 → 合成の保存
どちらも今は不必要に見えるけど、ファンクターを作ってコードを書く段階になるとファンクター則を守ったほうがよいらしいです。
11.3 アプリカティブファンクターを使おう
また型クラスの定義が示されます
class (Functor f) => Applicative f where pure :: a -> f a (<*>) :: f ( a -> b ) -> f a -> f b
今度は
・pure という関数は a を受け取ったら f a の形で型コンストラクタを実体化して返す
・(<*>)という関数はfmapとほぼ同じだけど、引数に関数の入っているFunctor値と値の入っているFunctor値を入れる点が違う
例としてghciのコンソールが出てるんだけどこれがまたよくわからない現代魔法
Prelude> Just (+3) <*> Just 9 Just 12 つまり ・Just (+3) が f (a -> b) を意味し ・Just 9 が f a に当たると思う
またここの解説を見るwww.geocities.jp
Maybeの定義はこうなってるはずなので
class Functor f => Applicative f where pure :: a -> f a (<*>) :: f (a -> b) -> f a -> f b -- Maybe instance Applicative Maybe where pure x = Just x Nothing <*> _ = Nothing (Just f) <*> x = f <$> x
これを順番に紐解いてみると
1. <*> の定義 (<*>) :: f ( a -> b ) -> f a -> f b -- シグネチャーは定義を示す看板に過ぎず、これだけでは凡人はなにもわからん (Just f) <*> x = f <$> x -- 型クラスの定義がプログラマーにとっての道しるべになる 2. 引数がJust であった時のApplicativeのInstanceの定義にしたがって式を置き換え Just (+3) <*> Just 9 -- 計算の対象の式 →(Just f) <*> x = f <$> x -- 左辺は右辺に等しいので →(+3) <$> Just 9 -- 中置演算子「<$>」を使った式に変換された 3. 中置演算子「<$>」の定義にしたがって式を置き換え (<$>) :: (Functor f) => ( a -> b) -> f a -> f b -- 「<$>」の定義はこれでした(すごいHaskell p.244) f <$> x = fmap f x → (+3) <$> Just 9 = fmap f x -- 実際の値に置き換えるとfmapを使った式に変換されました → fmap (+3) (Just 9) 4. fmapの定義にしたがって式を置き換え instance Functor Maybe where -- fmap :: (a -> b) -> Maybe a -> Maybe b fmap f Nothing = Nothing fmap f (Just x) = Just (f x) -- fmapのInstance定義はこれでした → fmap (+3) (Just 9) = Just (f x) → Just (+3 9) → Just 12
これで文脈がついたデータ同士を演算することが出来るようになったわけです。つまりMaybe同士(Just/Nothing)を演算。Scalaで言えば(Some/None)でしょうか。
もう少し、実利的な側面について追記するつもりですがとりあえずここまで。型クラスの定義を見るに、Applicative Functorは内部でfmapを呼び出しているようにしか見えません。しかしこれで残る理解すべき概念はモノイドとモナドだけです。とは言え、MaybeがMaybeモナドとか呼ばれているのを見るに、結局はここの話の延長にある概念でしか無い気がしてきました。