『プログラミング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 -> b
f c
はa -> c
f (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 c
m
を部分適用済みの関数(a ->)
と見なすと、
m b
はa -> b
b -> m c
はb -> a -> c
m 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
*1:12.5「練習問題」の2, 3, 6
*2:一般的にはrを使って(r ->)と書くが、ここでは本の記法にしたがって(a ->)とする。参考:"Functors, Applicative Functors and Monoids"
*3:12.5「練習問題」の3より