関数を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 ba -> b
  • f ca -> c
  • f (b -> c)a -> b -> c

となる。よって

(<*>) :: (a -> b -> c) -> (a -> b) -> (a -> c)

となる。

結果の型がa -> cという関数なので、ある引数fgに関して、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>>=を定義する必要があるが、returnpureと同じなので>>=だけ考える。

型から考えると、Monad型クラスで>>=の型は次のように定義されている。

(>>=) :: m b -> (b -> m c) -> m c

mを部分適用済みの関数(a ->)と見なすと、

  • m ba -> b
  • b -> m cb -> a -> c
  • m ca -> 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より

*4:Haskell 状態系モナド 超入門 - Qiita