Chapter 12

2.6 型の例

さのたけと
さのたけと
2021.05.04に更新

一度,型は集合であると認めることができたら,割と "エキゾチックな型" も考えられるようになる.例えば,空集合に対応する型はなんだろうか? それは Haskell では Void と呼ばれるものだ(ただしこれは C++ の void とは別のものだ).この型には何の値も属さない.Void を引数に取る関数を定義することはできるが,それを呼び出すことはできない.なぜなら,それを呼び出すためには,Void 型の値を渡さなければいけないが,それは存在しないのだから.この関数が返す値については何の制約もない.どんな型の値も返すことができる(ただし実際に返すことはない,なぜなら呼び出せないから).言い換えれば,この関数は戻り値に関して多相的だ.Haskeller たちはこれに名前をつけている:

absurd :: Void -> a

(a はどんな型も表せる型変数だと思い出そう.) この名前がついているのは偶然ではない.Curry-Howard 同型と呼ばれる対応によって,型と関数は論理学によるもっと深い解釈がある.型 Void は偽を表し,関数 absurd の型は「偽の仮定からは任意の帰結が導かれる」という主張と対応している.このことをラテン語の格言では "ex falso sequitur quodlibet" という.

次は一点集合に対応する型だ.この型はたった一つの値が属する.その値はただ "ある".あなたはすぐに気づかないかもしれないが,これが C++ の void だ.この型を引数にとる関数を考えてみよう.この関数は常に呼び出すことができる.もしそれが純粋関数だったら,常に同じ結果を返す.次がこの関数の例だ:

int f44() { return 44; }

あなたはこの関数は "無" (nothing) を引数に取る関数だと思うかも知れないが,先に見た通り "無" を取る関数は呼び出すことができない.ならばこの関数は何を取っているのだろう? 概念的には,この関数はたった一つしか存在しない持たないダミーの値を取っており,従ってわざわざ明記しなくても良いのだ.しかし Haskell では,この値のための記号が存在する:それは空のカッコ () だ.そういう訳で,不思議な偶然によって(本当に偶然か?),引数を取らない関数の呼び出しは C++ と Haskell で同じ形をしている.さらに Haskell はその簡潔さへの愛のため,その型,コンストラクタ,そしてその唯一の値は全て同じ () で表される.上の関数は Haskell ではこう書かれる:

f44 :: () -> Integer
f44 () = 44

一行目は関数 f44 は型 () ("unit" と読む) を取り,Integer を返すと宣言しており,二行目は f44 を次のように定義する: unit の唯一のコンストラクタ () に対するパターンマッチによって,数 44 を返す.この関数を呼び出すには,unit の値 () を与えて:

f44 ()

とする.ここで,unit を引数に取る関数は,戻り値型の値を一つ選ぶことと同等だ(上の例では Integer44 を選んでいる).つまり,関数 f44 は数 44 の別の表現だとみなすことができる.この見方によって,集合の要素について述べる代わりに,関数(射)でそれを表すことができるのだ.任意の型 A に対して,unit から A への関数は,A の要素と一対一に対応する.

それでは戻り値に void を取る関数はどうだろうか? C++ ではそのような関数は副作用を持つもののために使われるが,それらは数学的な意味では関数ではない.unit を返す純粋関数は何もしない:受け取った引数を捨てるだけだ.

数学的には,A から一点集合への関数は,A の全ての要素を一点へ送る.全ての A に対して,そのような関数は唯一存在する.Integer に対するそのような関数がこれだ:

fInt :: Integer -> ()
fInt x = ()

この関数にどんな整数を与えても,unit を返す.再び簡潔さの精神に基づいて,Haskell では捨てられる引数に対してはアンダースコア _ によるワイルドカードパターンが適用できる.こうすることで,その引数に名前を与える必要がなくなる.上のコードはこう書き直せる:

fInt :: Integer -> ()
fInt _ = ()

この関数の実装は,引数の値に依存しないだけでなく,引数の型にも依存していないことに気づいて欲しい.同じ式で実装できる関数は パラメータ多相的 (parametrically polymorphic) と呼ばれる.具体型の代わりに型パラメータを使うことで,そのような関数の族を一挙に定義することができる.任意の型から unit への多相関数は何と呼ばれるべきだろうか? もちろん,unit だ.

unit :: a -> ()
unit _ = ()

C++ なら次のように書ける:

template<class T>
void unit(T) {}

次は二元集合だ.C++ ではそれは bool と呼ばれる.Haskell では,予想通り,Bool がそれだ.違いは,C++ では bool はビルトイン型であるのに対して,Haskell では次のように定義することができる:

data Bool = True | False

(この定義の読み方はこうだ:BoolTrueFalse のいずれか.) 原理的には,C++ でも Boolean 型は列挙型として実装できる:

enum bool {
    true,
    false
};

だが,C++ で enum は実は整数なのだ.C++11 の enum class を使うこともできるが,そうすると値をクラス名で評価しなければいけない: bool::truebool::false のように.しかもこれらを使う全てのファイルで適切なヘッダを include しなければならない.

Bool を取る純粋関数は,終域から二つの値を選ぶだけで良い: True に対して一つ,False に対してもう一つ.

一方で Bool を戻り値に持つ関数は 述語 (predicate) と呼ばれる.例えば,Haskell のライブラリ Data.Char はこのような述語関数が山ほどある: isAlphaisDigit のように.C++ にも類似のライブラリ [1] があり isalphaisdigit などが含まれるが,これらは Boolean ではなく int を返す.これらの実体は std::ctype にあり,ctype::is(alpha, c), ctype::is(digit, c) などと定義されている.

脚注
  1. 訳注:原文もなぜかここは空になっている ↩︎