なんとな~くしあわせ?の日記

「そしてそれゆえ、知識そのものが力である」 (Nam et ipsa scientia potestas est.) 〜 フランシス・ベーコン

すごいHaskell たのしく学ぼう 読解3

すごいHaskell延長戦です。
あまりHaskell自体を使う気はないので、概念だけ読み取って実際的な話は飛ばす。

すごいHaskellたのしく学ぼう!

すごいHaskellたのしく学ぼう!

第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モナドとか呼ばれているのを見るに、結局はここの話の延長にある概念でしか無い気がしてきました。