Functor, Applicative, Monad
TL;DR
冬休みの宿題として、関数型言語の基礎を勉強しようとしてなんとなくしか理解できてなかった、Monad とか Functor とかその辺を勉強した。
ずいぶん長いこと積んでた、プログラマのための圏論の上中下を読んだり、Haskellに入門したりした
勉強の方法
目的は、Functor とか Monad らへんの知識を勉強することだった。
振り返ってみると以下のように勉強すると良いように思う。
まず、 Functors, Applicatives, And Monads In Pictures を読む。
Functor くらいまでは何を言ってるかわかるが、 Haskell で説明コードが書かれてることもあり段々わからなくなるので、Learn You a Haskell for Great Good! を読みつつ Haskell の環境構築をして書いてみたりする。
個人的には、 :t
という型を出力するコマンドをめちゃくちゃ多用したので頭の片隅に入れておくと良さそう。
そのあと、Learn You a Haskell for Great Good! - Making Our Own Types and Typeclasses を読んで Functor を理解する。
引き続き Learn You a Haskell for Great Good! - Functors, Applicative Functors and Monoids を読んで Applicative と Monoid を理解する。
最後に Learn You a Haskell for Great Good! - A Fistful of Monads を読んで Functors, Applicatives, And Monads In Pictures を再度読んでみると不思議と概念を理解した感じがする。
そして余裕があれば、以下のもっと理論的な圏論の背景などを読むとよい (自分はこれを最初に読んで時間がかかった感覚がなんとなくある) - プログラマーのための圏論(上) - プログラマーのための圏論(中) - プログラマーのための圏論(下)
自分用のまとめでもあるメモ
Functor
Learn You a Haskell for Great Good! - Making Our Own Types and Typeclasses#The Functor typeclass
class Functor f where
fmap :: (a -> b) -> f a -> f b
引数が、 (a -> b)
と f a
(type parameter a を 持つ f) で returnが f b
(type parameter b を 持つ f)
例えば、配列の操作をするmapは
map :: (a -> b) -> [a] -> [b]
であり、
instance Functor [] where
fmap = map
[]
は Int などの type parameter を取る型なので条件を満たしてる ( [a]
は f a
)
なので、 map
は 配列のための fmap
と言える。
Functorは、以下の規則を守る法則を守る必要があり、Haskell だとこれを守るのはユーザーとなってる > 1. if we map the id function over a functor, the functor that we get back should be the same as the original functor > 2. composing two functions and then mapping the resulting function over a functor should be the same as first mapping one function over the functor and then mapping the other one
1は、 fmap id = id
を満たすこと
2は、 fmap (f . g) = fmap f . fmap g
を満たすこと
Applicative Functor
1つの引数を受け取るようなfunctionのマッピングは、Functorで十分機能するが、複数の引数を取る関数はどうだろうか
$ fmap (*) (Just 3)
# -> Just (* 3)
(Just (* 3)) (Just 5)
のような計算方法を知らないのでこれ以上一般には、計算できない。
class (Functor f) => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
要するに
# Functor
(*) <$> Just 2 <*> Just 8
# Applicative Functor
Just (*) <*> Just 2 <*> Just 8
Monad
モチベーションは、Applicative Functor とは逆で a -> m b
のような function を m a
に適用して m b
を返すにはどうするか
そんな関数を bind と呼ぶ、また bind の定義は、
(>>=) :: (Monad m) => m a -> (a -> m b) -> m b
Monad の 定義は以下
class Monad m where
return :: a -> m a
(>>=) :: m a -> (a -> m b) -> m b
(>>) :: m a -> m b -> m b
x >> y = x >>= \_ -> y
fail :: String -> m a
fail msg = error msg
しかし return
と (>>=)
以外はほとんどユーザーレベルが触ることはない。
Monad は、合成の容易性が高い 例えば、
# Pair の左と右に値を追加する処理
addLeft n (left,right) = (left + n,right)
addRight n (left,right) = (left,right + n)
return (0,0) >>= addRight 2 >>= addLeft 2 >>= addRight 2
# -> (4,2)
またラムダ式との組み合わせが良い
Just 9 >>= \x -> return (x*10)
# -> 90
# ネストしたラムダ式
Just 3 >>= (\x -> Just "!" >>= (\y -> Just (show x ++ y))
# -> Just "3!"
# Just は Maybe なので Nothing に置き換えても動く
Just 3 >>= (\x -> Nothing >>= (\y -> Just (show x ++ y))
# -> Nothing
ラムダ式との相性がいいので、do式 という syntax が容易されている
foo :: Maybe String
foo = do
x <- Just 3
y <- Just "!"
Just (show x ++ y)
更なる学習に向けて
State Monad など いわゆる Monad の実際の例などは、Learn You a Haskell for Great Good! - For a Few Monads More を読むと良い