bitterharvest’s diary

A Bitter Harvestは小説の題名。作者は豪州のPeter Yeldham。苦闘の末に勝ちえた偏見からの解放は命との引換になったという悲しい物語

極限-HaskellでのReaderを定義する

3.2 \(Reader\)を定義する

Haskellでは\(Reader\)を用意している。Control.Monad.Readerというモジュールを読み込めば、使えるようになっている。しかし、このモジュールを理解しようとすると、忍耐力を必要とする。汎用性を高めるために、\(Reader\)が、\(ReaderT\)と呼ばれるデータ型を利用して、定義されているためだ。このため、\(Reader\)を理解しようとすると、別のいくつかのことを理解しなければならない。この作業は、煩わしく、下手をすると、何を理解しようとしていたのかさえ忘れてしまう。

そこで、ここでは、\(Reader\)の理解を助けるために、Haskellでの定義から離れて、必要な事項だけで説明することとしよう。

前回の記事で、\(Reader\)は

type Reader u a 

と理解して、利用した。これは、\(u\)という環境を与えて、\(a\)を出力するというものであった。\(Reader\)は、入力を\(u\)とし\(a\)を出力する関数を、データ型としたものと考えてよい(圏論では、データ型は対象である。関数を対象に選べるのも、圏論の汎用性、有用性を示す証左である)。そこで、次のように定義しよう。

data Reader u a = Reader (u -> a)

Haskellで用いられている用語の説明をしておこう。等式の左側に存在する\(Reader\)は型コンストラクタ(type constructor)と呼ばれる。また、\(u\)と\(a\)は型引数(type argument)と呼ばれる。\(data Reader \ u \ a\)により、データ型\(Reader\)は、データ型\(u\),\(a\)を用いて定義されていることを示す。

等式の右側の\(Reader\)は値コンストラクタ(value constructor)あるいはデータ・コンストラクタ(data constructor)と呼ばれる。これに続く\( \ u \rightarrow \ a\)は型と呼ばれる。そして、関数となっているので、特に、代数的データ型と呼ばれる。

上の定義を用いて、データ型\(Reader\)の値をいくつか求めて見よう。

Prelude> data Reader r a = Reader (u -> a)
Prelude> a = Reader $ \u -> 0.5 * u -- 数値を入力し、その半分を求める関数。もちろんa = Reader (*0.5)と入力しても同じ
Prelude> :t a
a :: Fractional a => Reader a a
Prelude> b = Reader $ \u -> show u – 数値などを入力し、その文字列を求める関数
Prelude> :t b
b :: Show a => Reader a String
Prelude> c = Reader $ \u -> show (u* u) – 数値を入力し、その2乗の文字列を求める関数
Prelude> :t c
c :: (Show a, Num a) => Reader a String
*Main> d :: Reader Int String ; d= Reader $ \u -> show ((*2) u) – 正数を入力し、その2倍の文字列を求める関数。これはd = Reader $ show . (*2)と同じ
*Main> :t d
d :: Reader Int String

1)ファンクタとしての定義

データ型が用意できたので、このデータ型を用いる関数を用意しよう。ファンクタから始めよう。

関手\(Reader\)は、与えられた関数を\(Reader\)というコンテナで包むものである。

データ型が用意できたので、このデータ型を用いる関数を用意しよう。ファンクタから始めよう。ファンクタでは、\(fmap \ f\)という関数を用意する必要がある。

圏\(\mathcal{C}\)は、関手\(Reader \)によって圏\(\mathcal{C’}\)に移されるものとしよう。そして、圏\(\mathcal{C}\)は、射の集まりで構成され、それらは\(U\)から\(X\)への射(\(r:U \rightarrow X\))としよう。また、任意の射\(r\)は、関手\(Reader\)によって圏\(\mathcal{C’}\)の射にコンテナをかぶせられた射\(Reader \ r\)に写されるものとしよう。

なお、コンテナをかぶせられた射は、関数\(runReader \ (Reader \ r) \ u\)を実行することにより、\(r(u)\)の値を出力する。
これらの関係を示すと下図になる。
f:id:bitterharvest:20180114104956p:plain
次に、圏\(\mathcal{C}\)内に別の射\(f\)が存在し、これは、射\(r\)を別の射\(s\)に写すものとする。このとき、\(s = f . r\)となる。また、\(f\)を関手\(Reader\)で写したものを\(fmap \ f\)としよう。これらから、次のような可換図式を得る。
f:id:bitterharvest:20180114105557p:plain
プログラムで示した例\(r =(*2),f=show\)の可換図式を求めると下図のようになる。
f:id:bitterharvest:20180114111313p:plain
それでは、Haskellでのファンクタの定義を示そう。

instance Functor (Reader r) where
  fmap f (Reader r) = Reader $ f.r

なお、ファンクタとして定義するとき、\(Reader\)が関手なので、\(r\)をつけずに、

instance Functor Reader where
  fmap f (Reader r) = Reader $ f.r

と定義しそうになるが、\(fmap\)の定義において、\(Reader\)ではなく\(Reader \ r\)を用いる必要があるので、\(r\)をつけないと誤りとなる。

2)モナドとしての定義

次はモナドでの関数を定義しよう。本論に入る前に、モナドがどのような数学的な概念であったかを復習しておこう。モナドとは、ある圏\(\mathcal{C}\)のモナド\(M\)とは、\(\mathcal{C}\)の対象\(A\)をコンテナ\(M\)で包んだものであり、それは\(M \ A\)と記述される。モナドには2つの関数が用意されていて、一つはモナドを作り出すもの、他の一つは、モナドからモナドを作り出すものである。二つの関数の用意の仕方にはいろいろあるが、Haskellでは\(return\)と\((>>=)\)を用意することになっている。それでは、下図を用いながら説明しよう。
f:id:bitterharvest:20180114111628p:plain

\(return\)は圏\(\mathcal{C}\)の対象\(A\)をモナドとしてのコンテナで包むことである。これを、\(return\)の型シグネチャで確認しよう。

:t return
return :: Monad m => a -> m a

例をいくつか挙げておこう。[ ] はモナドなので、整数から各括弧の付いたモナドを作成してみよう。

Prelude> a = return 3 :: [Int]
Prelude> a
[3]
Prelude> :t a
a :: [Int]

\(Maybe\)もモナドなので、整数から\(Maybe\)のモナドを作成してみよう。

Prelude> b = return 3 :: Maybe Int
Prelude> b
Just 3
Prelude> :t b
b :: Maybe Int

次の関数は、\((>>=)\)である。これは、二つの引数(\(M \ A,F\)))が与えられる。一つはモナドの値である。もう一つは関数である。この関数\(F\)は少し変わっていて、モナドにする前の値、即ちコンテナに包まれる前の値\(A\)を用いて、これをモナド\(M B\)に変換する。式で表すと、\(F: A \rightarrow M \ B\)である。型シグネチャで確認しておこう。

Prelude> :t (>>=)
(>>=) :: Monad m => m a -> (a -> m b) -> m b

それでは、いくつかの例を見ていこう。まず各括弧の場合だ。\(f\)の関数は、整数が与えられたならば、それに4を加えて各括弧でくくることにしよう。次のようになる。

Prelude> a = return 3 :: [Int]
Prelude> f = \r -> [r + 4]
Prelude> c = a >>= f  -- 中置関数として
Prelude> c
[7]
Prelude> d = (>>=) a f  -- 前置関数として
Prelude> d
[7]

次は\(maybe\)の例を示そう。整数が与えられた時、それを2倍にし、モナド\(Maybe\)にして返す関数を考えよう。

Prelude> a = return 3 :: Maybe Int
Prelude> f = \x -> Just $ (*2) x 
Prelude> (>>=) a f
Just 6

モナドに慣れてきたので、\(Reader\)に対しても、\(return\)と\((>>=)\)を定義することとしよう。

まず、\(return\)から始めよう。次のように定義されている。

return x = Reader $ \u -> x

上の定義では、\(u\)の値に関わらず、\(x\)の値が定まるので、\(\backslash u -> x\)は定数関数となる。

いくつかの例を挙げてみよう。 \(return\)で出力値を与えて、\(runReader\)で実行するだけの簡単なものだ。

*Main> a = return 3 :: Reader Int Int
*Main> :t a
a :: Reader Int Int
*Main> runReader a 6
3
*Main> b = return 3 :: Reader String Int
*Main> :t b
b :: Reader String Int
*Main> runReader b "Three"
3
*Main> c = return "Jack" :: Reader String String
*Main> :t c
c :: Reader String String
*Main> runReader c "Betty"
"Jack"
*Main> 

次は、\(>>=\)である。モナドでの定義は次のようになっている。

*Main> :t (>>=)
(>>=) :: Monad m => m a -> (a -> m b) -> m b

これをReaderで置き換えると次のようになる。

*Main> :t (>>=)
(>>=) :: Reader r -> return . s -> reader g

それでは、下図を参考にしながら、(>>=)を作成していこう。
f:id:bitterharvest:20180115092811p:plain

\( ( >>=)\)は基本的には、二つの関数の合成である。上の図であれば、\(Reader \ r\)と\(Reader \ s\)との合成である。\(Reader \ r\)を実行して、その出力を\(Reader \ s\)の入力として渡せばよい。入力となる値は、\(runReader (Reader \ r) \ u\)である。しかし、\(Reader \ s\)はこれを受け取れるようにはなっていないので、可能な形に変換する必要がある。それには、受け取った入力を\(s\)で変換し、それを戻す定数関数を作ればよい。これをしてくれるのは、先ほど示した\(return\)である。そこで、\(return . s\)とすればよい。従って、\(>>=\)への入力は、\(Reader \ r\)と\(f=return . s\)となる。

\(>>=\)からの出力は、\(Reader \ g\)である。そして、\(g\)は、入力を\(\backslash u\)とし、出力を\(runReader (f \ (runReader (Reader \ r) \ u)) \ u \)とする関数である。

従って、\(>>=\)は、\(Reader \ r\)を\(x\)で置き換えると次のように定めることができる。

x >>= f  = Reader $ \u -> runReader (f (runReader x u)) u

少し、用いてみよう。\(r=(*2),s=show\)の場合には、次のようになる。

*Main> a = Reader (*2) >>= return . show
*Main> runReader a 5
"10"

\(r=show,s=length\)の場合には、次のようになる。これは、入力した数字の桁数を求める関数である。

*Main> b = Reader show >>= return . length
*Main> runReader b 2018
4

最近のHaskellでは、モナドの上位クラスにアプリカティブが用意されている。従って、\(Reader\)と定義するためには、これも必要となる。ここでは、説明を省いて、アプリカティブを含めて、\(Reader\)に関連する定義を示そう。

import Control.Monad
import Control.Applicative

data Reader u a = Reader (u -> a)

instance Functor (Reader r) where
  fmap f (Reader r) = Reader $ f . r     

instance Applicative (Reader r) where
  pure x          = Reader $ \u -> x
  (Reader f) <*> (Reader x) = Reader $ \u -> (f u) (x u)

instance Monad (Reader r) where
 return x = Reader $ \u -> x
 x >>= f  = Reader $ \u -> runReader (f (runReader x u)) u

runReader :: Reader u a -> u -> a
runReader (Reader f) u = f u

ask :: Reader a a
ask = Reader $ \u -> u