成らぬは人の為さぬなりけり

エンジニアライフをエンジョイする為のブログ

すごい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なので、そこで理解できる事を期待。
(そこまで読んでからブログ書けば良かったのか、、、)