すごいHaskellたのしく学んでみる-その8
今回はついにFunctorです。
正直、まだFunctorの利点がよくわかっていないですが、
とりあえず、書いてみようと思います。
今回のテーマは、、、
- Functorとは?
- fmap
- ファンクター則
- ApplicativeFunctor
こんな感じでお送りしようと思います。
Functorとは?
「全体を写せるものの型クラス」だそうです。
「何かしら変換処理を施して、別の値にする」感じでしょうか。
※「写す」というのはmap overを日本語訳した感じらしいです。
すごーく簡単に言うと、
ある値に関数を適用して別の同じ型の値にする
みたいなイメージを持っています。
fmap
上でお話した処理を行うのが、fmap関数です。
Functor型クラスのインスタンスはこいつが必ず実装されています。
では、使ってみます。
Prelude> fmap (*2) [1,2,3] [2,4,6] Prelude> fmap (+1) (Just 1) Just 2
リストに対するfmapはmapと同じなようです。
instance Functor [] where fmap = map
ファンクター則
Functorには2つルールがあります。
ただ、このルールはコンパイラがチェックしてくれるルールでは無い為、
コードを書く上で、意識する必要があります。
第一法則
「id関数で写した場合、ファンクター値が変化しないこと」
要は
fmap id
ってやった場合、同じ値にならないといけませんよ、ということです。
やってみます。
Prelude> fmap id $ Just 1 Just 1 Prelude> fmap id $ Just 100 Just 100 Prelude> fmap id Nothing Nothing Prelude> fmap id [1,2,3,4,5] [1,2,3,4,5]
第二法則
「fとgの合成関数で写したファンクター値と、gで写したファンクター値をfで写したファンクター値は同じでなければならない」
つまり、関数を別々に適用した場合と、合成して適用した場合で結果が違っちゃいけませんよ、ということです。
法則を破ってみましょう
あまり意識していなくても、多分なんとなく勝手に法則を守っている気がするので、
あえて破ってみます。
data MyMaybe a = MyNothing | MyJust Int a deriving (Show) instance Functor MyMaybe where fmap f MyNothing = MyNothing fmap f (MyJust i x) = MyJust (i+1) (f x)
Maybeを自分で作ってみました。
これを写してみましょう。
*Main> fmap id $ MyJust 0 "hoge" MyJust 1 "hoge"
idを適用する前と値が変わっていますね。これは第一法則違反ですね。
ApplicativeFunctor
通常、Functorの値が関数の場合、それを抜き出して、別のFuncotorを写すということは出来ません。
コンストラクターでパターンマッチすれはできますが、それだと特定の型に依存してしまいます。
このような場合はApplicativeFunctorなるものを使うそうです。
たとえば、
Just ("Hi, " ++ ) Just "yagince"
一個目のFunctorが持っている関数に二個目のFunctorの値を適用して、
Just "Hi, yagince"
にしたい、ような場合。
※それってどういう時?というのは置いといて、、、
まずは、Applicativeの実装はこんな感じのようです。
class (Functor f) => Applicative f where pure :: a -> f a (<*>) :: f (a -> b) -> f a -> f b
Maybeは既にアプリカティブファンクターだということで、
Maybeを使って実験してみたいと思います。
instance Applicative Maybe where pure = Just Nothing <*> _ = Nothing (Just f) <*> something = fmap f something
こんな感じになっているそうです。
- pureはJustを返す。
- 第一引数がNothing、つまり関数が無い場合は、Nothingを返す
- 第一引数が関数を持っている場合は、その関数に第二引数を適用する。
ちょっとひっかかるのは、3つ目。
somethingがNothingだったらどうなる?と疑問に思ったんですが、
そういえば、MaybeのfmapはNothingの時は、Nothingなので、大丈夫でした。
では、何か書いてみます。
Prelude Control.Applicative> Just (+3) <*> Just 9 Just 12 Prelude Control.Applicative> pure (*10) <*> Just 9 Just 90 Prelude Control.Applicative> Just ("Hi, " ++ ) <*> Just "yagince" Just "Hi, yagince"
できました。
アプリカティブ・スタイル
<*>が連続するような書き方をアプリカティブ・スタイルというそうです。
例えば、、、
Prelude Control.Applicative> Just (++) <*> Just "Hi, " <*> Just "yagince" Just "Hi, yagince"
こんな感じ。
<$>
アプリカティブファンクターのpure関数で
pure f <*> x
これは
fmap f x
これと等しいです。
つまり
pure f <*> x <*> y ....
これは
fmap f x <*> y ....
こうする事ができます。
このパターンは頻出パターンらしく、中置関数が用意されています。
それが「<$>」です。
内容的には、単純にfmapのエイリアスみたいな感じです。
なので、先ほどの例は、このように書き直せます。
Prelude Control.Applicative> (++) <$> Just "Hi, " <*> Just "yagince" Just "Hi, yagince"
つまり、Just StringとJust Stringをくっつけたい、みたいな事がこのように書けるわけですね。
「これってすごくない?」って書いてあるんですが、
んー、正直まだ用途のイメージがつかないというか、
そこまで利益があることなんだろうか、と思ってしまっている感じです。
ここから10ページ程ApplicativeFunctorなので、そこで理解できる事を期待。
(そこまで読んでからブログ書けば良かったのか、、、)