top page > computer > haskell > web_lecture > for_programmer > memo_type.html
更新日:
文責: 重城良国

型についての再考

Haskellの型とは何か

型とは何か。集合から「部分集合」という概念を引き算したもの、なのかもしれない。部分集合は作れないが直積と直和を作ることができる。ここらへんはタプルとEitherによって表現可能だ。しかし、newtypeによって作られる新しい型はいったいどのように考えられるだろうか。

タプル(15, 23)は以下のような関数と内容的には同じだろう。

t = \f -> f 15 23

そして要素を取り出す関数はこう書ける。

fst t = t $ \a b -> a
snd t = t $ \a b -> b

Either型は何を意味するのだろうか。直和であることはたしかだ。Cで言えばunionだ。LispやRubyでは実のところすべての型がEitherでまとめられているようなものだ。LispやRubyでの値は以下のようになる。

Int | Double | Char | String | SomeType | OtherType | ...

Eitherは単に型のレベルのお話なのだろうか。

data Either a b = Left a | Right b

型というものは「それを扱う関数」があってこそのものなのかもしれない。以下の2つの関数について考えてみよう。

idi :: Int -> Int
idi n = n
idc :: Char -> Char
idc c = c

やっていることは同じだ。とれる値の型が違う。単純な代数的データ型について考えてみよう。

data X = A | B | C
data Y = D | E
fun :: X -> Y
fun A = D
fun B = E
fun C = D

たとえばこのときにfun Dとかするのは「許されない」のか「意味を持たない」のか。本質的には「意味を持たない」と考えるべきなのだろう。たとえば「椅子は偶数か奇数か」と言うのと同じだ。「集合場所は猫です」と言うのと同じだ。言ってみれば自然言語は「型」を持っているということだ。

newtype Human = Human Int
data Gender = Man | Woman

gender :: Human -> Gender

人をID番号で表現するとしてそれぞれの人の性別を調べる関数genderがあるとする。このとき関数genderがHuman型をとりInt型をとれないということはどのように考えることができるだろうか。IntとHumanはパラレルワールドに存在するような感じか。

「型とは何か?」は感覚ではよくわかるのにいざ説明しようとするとよくわからないな。

「型とは何か?」「...わからない」

値の集合と考えるのが一番妥当なのかもしれない。しかし、すこし違う気もする。集合と考えるとその境界は任意であるし集合同士が交わることもある。ただ型も直和が作れるので多少集合らしさはある。

たとえば「'a'は偶数である」という命題は真でも偽でもなく無意味だ。まあ厳密に言えば「'a'は文字であって偶数ではない」ので偽とも言える。ここらへんは自然言語の限界といった感じがある。「'a'を2で割ったあまりは0である」という命題のほうがいいかな。たとえば「優しさは何色か」といった問いも無意味だ。型というのはそういうあたりに関係してくるものだと思う。直観的理解としては、だ。

Char型とBool型のみにしぼって考えてみよう。

toLower :: Char -> Char
not :: Bool -> Bool

「型とは、どの関数の引数になるか、どの関数の返り値になるか、を決めるもの」と考えるのが妥当かもしれない。逆に言うと「関数なしには型もない」ということか。

たとえば以下の2つは内部的には同じものとして保存されているかもしれない。

data Gender = Man | Woman
data Direction = Forward | Backward

値のがわからでなく関数のがわから

値のがわから見ても型の本質はつかめないのかもしれない。「型」はその値を引数や返り値とする関数のがわから見るのが正解なのだろう。

まず関数はすべての値を対象にすることはできないということ。なぜなら、すくなくともHaskellにおいて新しい値を作ることが可能だから。

data X = A | B | C

data X = A | B
data Y = C

値をまとめて名前をつけるものが型、かも。関数が意味を持つのは特定の引数となる値が特定の型であるときだ。

説明の流れ

すべての値に適用可能な関数というものはすくない。たとえば値をその値自身に関連づける関数であればすべての値に適用可能だ。しかしたとえば「3を足す」関数であれば引数となる値は数である必要があるし、「小文字にする」関数であれば引数となる値は文字である必要がある。

値をそのまま返すなどの特別な場合以外においては新たに作られた値に対して対応することもできない。

関数では本質的に引数としてとる値がかぎられる。「型」という値の集合を指定してやることで、その関数の引数として正当であるかを機械的にチェックすることが可能になる。

関数とは値を値に関連づけるもの

関数とは値を値に関連づけるものである。たとえば値を2倍する関数であれば1を2に、2を4に、3を6に、それぞれ関連づける。

適用可能な引数はかぎられる

いろいろなタイプの値がある。それらの値すべてに適用可能な関数というものはあまりない。ほとんどの関数は特定のタイプの値にのみ適用可能だ。

個々の値をチェックするのは困難

適用可能でない値に関数を適用するとそれ以上の処理は続けられなくなる。そのようなことが起きないように事前に値を機械的にチェックできたら望ましい。しかし、ひとつひとつの値について、それぞれの関数で処理が可能かチェックするのは困難だ。

値を型というグループにまとめる

いろいろな関数はそれぞれ引数として同じタイプの値をとる。たとえば足し算と引き算はどちらも「数」という性質を持つ値を引数としてとるし、小文字にする関数と空白文字かどうかチェックする関数は「文字」という性質を持つ値を引数としてとる。

そのように同じ特徴を持つ値を名前をつけてまとめてしまおう。値をまとめるものを「型」と呼ぶ。

返り値の型を明示することでチェックを連鎖させられる

さらに関数からの返り値が「型」というまとまりの範囲内となることを保証してやる。そうすると型のチェックを連鎖させることができる。

正当なCSSです! HTML5 Powered with CSS3 / styling, and Semantics