学習の素材は、
すごいHaskellたのしく学ぼう!
この表記は、私見・感想です。
Functor 型クラス
ファンクターとは、関数で写せる(写像、map over) できるもののことをいう。例えば、リスト、Maybe、木がファンクターとなる。つまり、ある型aから別の型bへの関数 と ある型aに適用されたファンクター値 を引数にとり、 別の型bに適用されたファンクター値 を返すもののことをファンクターという。
また、ファンクターは文脈を持った値だ とみなすこともできる。Maybe 値は「計算が失敗したかもしれない」という文脈、リストは「複数の値を同時に取るかもしれない」という文脈。この時、fmap
はこの文脈を維持したまま、内部の値に対して、関数を適用してくれる(後述)。
class Functor (f :: * -> *) where
fmap :: (a -> b) -> f a -> f b
ある型を
Functor
のインスタンスにしたければ、fmap
ただひとつ実装すれば良い(追記)のではなく、ファンクター則を守る必要がある。ファンクター則とは、基本的な振る舞いが他のファンクターと一致することを保証するためにプログラマが遵守すべきルール。
ファンクターのインスタンス
Maybe
や、リスト、Either a
などがそれに当たる。
instance Functor Maybe where
fmap f (Just x) = Just (f x)
fmap f Nothing = Nothing
instance Functor [] where
fmap = map
instance Functor (Either a) where
fmap f (Left a) = Left a
fmap f (Right a) = Right (f a)
instance Functor Tree where
fmap f EmptyTree = EmptyTree
fmap f (Node a left right) =
Node (f a) (fmap f left) (fmap f right)
-- 値が再帰的な二分木構造になるかもしれない文脈
data Tree a = EmptyTree
| Node {value::a, left::(Tree a), right::(Tree a)} deriving (Show)
自作の木( 型引数が String
)について、fmap
を適用してみると次のように出力される。
*Main> fmap ("this is " ++) (Node "test" EmptyTree EmptyTree)
Node {value = "this is test", left = EmptyTree, right = EmptyTree}
fmap
の役割
ファンクターは、文脈の持った値であり、それは箱(に入った値)のようなものと表現することもできる。fmap
は、この文脈(≒ 箱)を保ったまま、引数に取った関数を中の値に適用することができる。
Prelude> :t fmap
fmap :: Functor f => (a -> b) -> f a -> f b
Prelude> fmap (++ " world!") ["Hello", "Good-by"]
["Hello world!","Good-by world!"]
fmap
と関数合成
ある1引数関数の型を表す際、アロー演算子を用いてa -> b
のように書く。この演算子は中置演算子だが、(->) a b
と表すこともできる。この時、(-> a)
もファンクターとなる。ここでの文脈は、 型 a
の値を適用すると、値が返ってくる となる。
fmap :: Functor f => (a -> b) -> f a -> f b
のf
を (-> r)
で置き換えてみると、
fmap :: (a -> b) -> ((->)r ) a -> ((->) r b)
(->)
を中置演算子として書き直すと
fmap :: (a -> b) -> (r -> a) -> (r -> b)
となる。fmap
の実装を見てみると、
fmap f g = (.)
-- or
fmap f g = (\x = f (g x))
であり、これは関数合成そのものとなる。
関数合成は、文脈を保って中の値に関数を適用するファンクターの一種、ということが言える。
fmap
のまとめ
fmap
とは、2つの観点で説明することができる。
- 関数とファンクター値を取って、その関数でファンクター値を写してから返す(
fmap :: (a->b) -> f a -> f b
) - 値から値への関数を取って、ファンクター値からファンクター値への関数を返す(
fmap :: (a->b) -> (f a -> f b)
)
特に、2番目の観点でファンクターを捉える時、fmap
することを 関数の持ち上げ(lifting) と呼ぶ。