関数をFunctor/Applicative/Monadにする
『プログラミングHaskell 第2版』の12章「モナドなど」の演習1で型 (a ->) をFunctor(関手)、Applicative、Monadにするという問題があり、少しわかりにくかったので、いまの自分自身の理解をまとめました。
型(a ->)とは
部分適用された関数の型を(a ->)(もしくは->を2引数関数とみて((->) a))と表現する2。この型の意味は「aを部分適用済みの関数」である。部分適用するのは、ある型をFunctorにするには型コンストラクタが持つ型変数が1つでなければならないことによる。
実際のインスタンス宣言はControl.Monadに存在する。
Functorにする
「型変数を持つならモナドになるか試してみるのが自然な流れ」と書いてあるので、(a ->)もMonadにしていく。
まずFunctorにする。Functorにするにはfmapを定義する必要がある。演習問題のヒントに書いてあるとおり、(a ->)に対するfmapの型がどうなるかを考える。Functor型クラスにおけるfmapの型は次のとおり。なお、aという変数は(a ->)で使われているので、これ以降新たに現れる型変数はbから始める。
fmap :: (b -> c) -> f b -> f cまず第2引数f bから考える。今回(a ->)をFunctorにするので、ある構造を表すfが(a ->)に該当する。つまり、fは「引数として取る型の値を返す部分適用済みの関数であること」を表す。つまりf bと書くとa -> bとなる。ここから(a ->)に対してfmapの型を次のように表現できる:
fmap :: (b -> c) -> (a -> b) -> (a -> c)このfmapの型をよく見ると、関数合成そのものになっている。よって、(a ->)は次のようにFunctorとできる。
instance Functor ((->) a) where fmap = (.)Applicativeにする
続いて、(a ->)をApplicativeにするにはpureと<*>を定義する。Applicative型クラスにおけるpureと<*>の型は次のとおり。
pure :: a -> f a(<*>) :: f (a -> b) -> f a -> f bまずpureから考える。型bを持つある値を(a ->)の文脈に持ち込むと考えると、型bの値を受け取り、つねにその値を返す関数(a -> b)を返せばよい。
pure :: b -> (a -> b)これはb -> a -> bと見なせるのでconstと同じである。
次に<*>も型から考える。これまでどおりfを部分適用済みの関数(a ->)と見なすと、
f bはa -> bf cはa -> cf (b -> c)はa -> b -> c
となる。よって
(<*>) :: (a -> b -> c) -> (a -> b) -> (a -> c)となる。
結果の型がa -> cという関数なので、ある引数fとgに関して、f <*> gは型aの1引数関数と考えればよい。また、関数a -> b -> cと関数a -> bに型aの値を適用すると、関数b -> cと型bの値になる。型cを返す関数を作る必要があるので、後者のbの値を関数b -> cに適用すればよい。これらを合わせて、次のように(a ->)をApplicativeにすることができる。
instance Applicative ((->) a) where pure = const f <*> g = \a -> f a (g a)ここではg aが型bの値、f aが関数b -> cである。
関数がApplicativeだと、二つの関数をそれぞれ適用して結果を足すコードを逐次的に書けたりする。
f = fmap (+) (+ 5) <*> (* 100) -- 5足した値と100かけた値の合計f 30 -- 3035また、pureと<*>はそれぞれSKIコンビネータにおけるKとSにあたる3。
Monadにする
最後に、(a ->)をMonadにする。Monadにするにはreturnと>>=を定義する必要があるが、returnはpureと同じなので>>=だけ考える。
型から考えると、Monad型クラスで>>=の型は次のように定義されている。
(>>=) :: m b -> (b -> m c) -> m cmを部分適用済みの関数(a ->)と見なすと、
m bはa -> bb -> m cはb -> a -> cm cはa -> c
となる。よって
(>>=) :: (a -> b) -> (b -> a -> c) -> (a -> c)となる。
Applicativeと同様に結果の型がa -> cという関数なので、(a -> b) >>= (b -> a -> c)は型aの1引数関数と考えればよい。Applicativeのときと同じように考えると、次のように(a ->)をMonadにすることができる。
instance Monad ((->) a) where return = pure f >>= g = \a -> g (f a) aここではf aが型bの値、gが関数b -> a -> cである。
関数がMonadだと、Applicativeで実現した「二つの関数をそれぞれ適用して結果を足すコード」を次のようにdo記法で書ける。入力した数値(この例だと30)を環境として持ちながら計算していると見なせる4。
f = do g <- (+ 5) h <- (* 100) return $ g + h
f 30 -- 3035脚注
-
12.5「練習問題」の2, 3, 6 ↩
-
一般的にはrを使って
(r ->)と書くが、ここでは本の記法にしたがって(a ->)とする。参考:“Functors, Applicative Functors and Monoids” ↩ -
12.5「練習問題」の3より ↩