メインコンテンツへスキップ
← Back to Blog

【Haskell】Functorのfmapについて

haskell memo

[注意]この記事はHaskellを学習中の者が書いたメモにあたる物です。

最近になってHaskellという言語をCis1941で学習しているのですが、その中でFunctorに講義資料で出会いました。

この関数が、かなり難解で苦戦しましたので自身の解釈と理解をはっきりと明文化しておこうと思いまして、この記事を作成しました。

よく、Monadと遭遇して多くの初学者が挫折するとweb上の情報では記されていますが、この関数(概念と呼ぶのが正しいのでしょうか?)もかなり難しいと感じています。

というか、基本的に再帰的な処理を書いたりとOOP手続型等とは頭の使い方が違うのに苦戦して、やっと慣れてきたところに、こういった概念が現れたら火傷するのも当然ですね(笑)。

あまり、ここら辺の愚痴を書いてしまうと、それだけで記事を書けてしまうので、序文はこの程度にしておきます。

fmapについて

class Functor f where
    fmap :: (a -> b) -> f a -> f b

-- 但し、f :: Type -> Type とする。

今回は上記のように定義されているとして話を進めています。上記は完全な定義2ではありませんが、他のメソッドについては、この記事の範疇を超えるため、ここでは扱いません。

一言で述べるとfmapは構造を保存したまま、関数fを値に適用します。

試しにMaybeで考えてみましょう。

instance Functor Maybe where
    fmap _ Nothing = Nothing
    fmap h (Just a) = Just (h a)

Maybeは実際にはMaybe Intといった、「おそらく、Intを持っているよね」みたいな文脈で用いられます。

これに関数f :: Int -> Intを適用するには、どんな操作が必要でしょうか?

  1. Maybe IntJust Intを保持しているかどうか確認する。
  2. もし、Nothingであれば関数fは適用できないからNothingを返す。
  3. Just Intを持っていれば、Intのみに関数fを適用する
  4. Justで再び包んで結果を返却する。

といった操作が必要になります。

これを愚直に実装してみると、

applyToMaybe :: (Int -> Int) -> Maybe Int -> Maybe Int
applyToMaybe f Nothing  = Nothing
applyToMaybe f (Just x) = Just (f x)
applyToMaybe (+1) (Just 5)   -- Just 6
applyToMaybe (+1) Nothing    -- Nothing

ということになるわけです。

構造を保存したまま関数を適用する。 というのが、なんとなくわかったでしょうか?しかし、これをMaybe StringMaybe (自作したデータ構造)毎に作成するのは骨が折れます。

そこでFunctorというclassMaybeでインスタンス化することで、Maybe **に何が入っていても対応できるというわけです。

省略形の書き方

fmap<$>で省略される場合が多いです。

(+2) <$> Just 5     -- Just 7
(+5) <$> [1,2,3,4]  -- [6,7,8,9]

fmapのユースケース

fmapは単一の関数適用を抽象化するものです、であるのでなんらかの構造を維持しながら関数を適用するケースで扱われます。

実際には引数が複数のケースが多いです、例えばJust 3Just 5を足し合わせる時など、引数が2種類以上になった際に、Applicativeが用いられます。

参考文献

Footnotes

  1. [2013 Spring]Cis194 Introduction to Haskell by University of Pennsylvania

  2. (<$)の定義が抜けています、詳しくは参考文献を参照してください。