「依存型とΣ-Δ-Π随伴、そしてカン拡張」の冒頭にて:
「依存積型」と「依存和型」に関しては、もうサンザンだよー、あったく...[snip]...
ふんとに「依存ナントカ型」って紛らわしいよねぇ。それで、「依存ナントカ型」と言うのはやめます。...[snip]...
今後は見たままのパイ型/シグマ型を使おうと思います。
依存型の紛らわしさに、僕はもうほんとにウンザリなんだけど、今後はトラブルが起きないように整理しておきます。
内容:
はじめに
依存型に関して、圏論的な解釈を試みた記事に次があります。
今回の記事は、内容的に過去記事と重複がありますが、過去記事を参照せずに読めると思います。また、あまり圏論は使わないようにしています。とはいえ、次の用語と記法は使っています。
- 「圏」「対象」「射」の定義くらいは知っていたほうがいいでしょう。
- は集合圏(集合と写像の圏)です。出てくる圏はこの圏だけです。
- は集合圏の対象類で、すべての集合からなる類です。
- は集合圏のホムセットですが、実体は関数〈写像〉の集合です。
今回強調したいことは、呼び名がインフレーション〈膨張・増大〉を起こしているだけで、依存型に関わる概念が少数であることです。次の4つを知っていれば十分です。
- シグマ型
- シグマ型のインスタンス=タグ付きデータ
- パイ型
- パイ型のインスタンス=タプル
成分、インデックス
ナニカを並べたり配置したりしたデータにおける“ナニカ”をなんと呼ぶか?
- 成分〈component〉
- 要素〈element〉
- 項目〈item〉
- エントリー〈entry〉
- フィールド値〈field value〉
- 属性値〈attribute value〉
- カラム値〈column value〉
- プロパティ値〈property value〉
- スロット値〈slot value〉
- もっとあるかも
ハァー、ここで既にウンザリ。これらに本質的な違いはなくて、要するに関数値のことです。「関数値」とは区別したいという感情や文脈を完全に無視するのもナンナンデ、「成分」だけは使うことにします。
複合データ(ナニカを並べたり配置したりしたデータ) の成分を取り出す構文(書き方)は:
- もっとあるかも
ここに出てくる の呼び名も色々ありますが、一律に「インデックス」で済ませます。インデックスは要するに関数に対する引数のことです。
- 複合データ = 関数
- インデックス = 関数への引数
- 成分 = 関数値
直積型、直和型、指数型=関数型
型とは、集合または集合に構造が載ったもの(順序集合とかモノイドとか)のことです。より一般には、適当な圏 の対象を「型」とも呼びます。つまり、型 = 対象 。ここでは、圏 として集合圏 を取るので、型 = 集合 です。
「積型」、「和型」だと曖昧多義語でトラブルになるので、直積型〈direct product type〉、直和型〈direct sum type〉を使います。集合圏を背景にしているなら 型 = 集合 なので、直積型 = 直積集合、直和型 = 直和集合。さすがにこれは誤解されないでしょう。
型(=集合) に対して と書かれる型(=集合)を指数型〈exponential type〉と呼ぶことにします。これも大丈夫だよな、と思ったら、Wikipediaの exponential type は複素解析の話だった。けど、function type へのリンクもありました。指数型〈exponential type〉 = 関数型〈function type〉 です。
次のような書き方もあります。(「関数」と「写像〈map〉」は同義語です。)
プログラミング言語だと、指数型(=関数型)を などと書きます。これは、射のプロファイル記述(域・余域の指定)と紛らわしいので僕は使いません。多くのプログラミング言語では、指数型(=関数型)とプロファイルの記法を意図的に混同しています。そのへんのことは「Haskellの二重コロン「::」とバインド記号「>>=」の説明」参照。
一般的には 型=対象 なので、指数型=指数対象 ということになります。 がデカルト閉圏なら 内の指数対象を考えることができます。nLab項目 exponential object 参照。
直積型とタプル/リスト
型(=集合) に対してその直積型は です。型(=集合) に対してその直積型は です。が、次の2つの直積型は同じではありません。
同じではないけど同型なので、通常は同一視しています。同一視のための対応は:
直積型のインスタンス〈要素〉をタプル〈tuple〉と呼びます。(ニュアンスは違うにしろ)タプルの同義語が山ほどあります。でも、「タプル」だけを使うことにします。
タプルの成分がすべて同じ型のときリストといいます。成分の型が で長さ(成分の個数)が であるリストの型は です。この記法は次のような意味です。
しかし、 を指数型(=関数型)と解釈することもできます(後述)。
タプルもリストも直積型〈直積集合〉のインスタンス〈要素〉ですが、リストはすべての成分が同一の型になっています。
直和型とタグ付きデータ
直積型に比べて、直和型は馴染みがない人が多いようです。型(=集合) に対する直和型 は次のように定義します。
ここで注目すべきポイントは:
は、共通部分がない2つの部分から構成され、片一方は と同型で、もう一方は と同型です。もともと に共通部分があっても、別コピーを作って引き剥がされます。例えば:
直和型 のインスタンス〈要素〉は、 または の形です。第一成分をタグ〈tag〉と呼びます。 はタプルですが、直和型のインスタンスであることを強調するときはタグ付きデータ〈tagged data〉といいます。
タグの集合として を使いましたが、2元集合ならなんでもかまいません。例えば、
実用性はないけど自然なタグは集合それ自身です。
タグ付きデータを、見てすぐそれと分かるように書く記法は幾つかあります。僕は または のようなアットマークを使う記法を使っています。例えば、ユーザーIDとしてニックネーム〈ハンドル〉か通し番号のどちらでもいい場合、ユーザーIDのデータ型は次のようになるでしょう。
ユーザーID型のインスタンスは とか とかになります。ストリングのダブルクォートを省略していいなら または です。
当然ながら、直和型、タグ、タグ付きデータに対する(ニュアンスは違うにしろ)同義語はありますが、同義語を列挙するのはやめます。ウンザリするだけのことなので。
リスト型
成分の型が であるリストの型は、リストの長さごとに のように書けます。ここで、 は空リストだけからなる単元集合で、 です( と思ってもかまいません)。
リストは実は関数です。そのことを説明するために、自然数 対して、 とします。特別な場合は:
リスト があるときに、その第成分は と書きますが、 と書いても同じことです。次は同値です。
- は、成分の型が で、長さが のリストである。
- は、余域が で、域が の関数である。
型〈集合〉のあいだの同型として書けば:
上記の同型の右辺は指数型(=関数型)です。
成分の型が で、長さが のリストの型を と書きます。つまり:
長さを固定したリストの型は実は指数型(=関数型)だったのです。関数型の要素を“関数”と呼ぶ(当たり前!)ので、リストは関数です。
すべての長さのリストを寄せ集めると、長さを固定しないリストの型ができます。それを と書きます。
ならば なので、タグ付きデータにする必要はないのですが、リストの長さをタグにすると次のようになります。
は無限直和の場合も使える記号です。もちろん、有限直和に使ってもかまいません。例えば:
もともと共通部分がないときでもタグを付けるのはバカバカしいのはそのとおりです。そこで次の約束をしましょう。
- もともと共通部分がない集合たちに対して、 は と同じ意味で使ってよい。
- 共通部分がある集合たちに対しては、適当にタグを付けた直和を で表す。
このようなご都合主義的約束をしても大丈夫なのは、結果として出来上がる型〈集合〉が同型になるからです。同型な型〈集合〉を神経質に区別する必要はありません。
[追記]リスト型の定義にまつわる問題が次の記事にあります。
[/追記]
型ファミリーとシグマ型
集合 上で定義されて値が集合である関数を、
- でインデックス付けられた集合族〈X-indexed family of sets〉
と呼びます。長いので「集合族」と省略します。集合=型 だったので「型族」、ですがなんか語感が悪いので「型ファミリー〈type family〉」とします。型ファミリーも関数なので:
- 型ファミリーの成分 = 集合値関数の関数値である集合 (成分 = 関数値)
- 型ファミリーのインデックス = 集合値関数の引数 (インデックス = 引数)
- 型ファミリーのインデックス集合 = 集合値関数の域
型ファミリー〈集合値関数〉のインデックス集合〈関数の域〉は何でもいいので、特に に取ると、型ファミリー ができます。ところで、域(インデックス集合)が である関数をリストと呼んでいたので、これは型〈集合〉のリストです。つまり、型のリストは型ファミリーの特別なものです。
例えば、型ファミリー は、長さ3の型のリストです。
番号の集合 とは限らない任意の集合 でインデックス付けられた型ファミリー に対して(無限かも知れない)直和型を定義できます。インデックス集合(関数の域) をタグの集合に使います。
次のように定義することもできます。
この定義に従うと:
(無限かも知れない)直和型のインスタンス〈要素〉は、タグ と値 のペアで、値 が型ファミリーの成分 に所属するものです。
(無限かも知れない)直和型の別名がシグマ型〈sigma type〉です。ここで、「依存ナントカ」という用語を使うとまたトラブルになるので、使う言葉は次のものに限定します。
- 型ファミリー
- 直和型(ただし、インデックス集合が無限も許す)
- シグマ型 = 直和型(ただし、インデックス集合が無限も許す)
- タグ付きデータ = シグマ型のインスタンス
パイ型
が型ファミリーのとき、成分達 をすべて足し合わせた型〈集合〉が(型ファミリーの)シグマ型でした。成分達 をすべて掛け合わせた型〈集合〉が(型ファミリーの)パイ型〈pi type〉です。
簡単な例 で説明します。先に説明した という書き方を使います。ファミリーは関数であり、番号集合からの関数はリストです。だから、番号集合を域〈インデックス集合〉とする型ファミリーは型のリストです。
成分が有限個である型ファミリーの場合は、そのパイ型は次のように定義してかまいません。
インデックス集合が無限になっても通用するように少し形を変えます。
パイ型の要素は関数 です。しかし、任意の関数を許すわけではなくて、条件を満たすものに絞ります。この条件を満たす関数 は の要素、つまりタプルと同一視できます。パイ型はタプルの型だと言えます。
に対する条件は次のようにも書けます。
これを使うと、型ファミリーのインデックス集合が無限の場合でもパイ型の定義が書けます。
こうして定義されるパイ型のインスタンスはサイズが無限かも知れないタプルです。タプルがリストと違うのは、インデックス〈引数〉ごとに値を取る集合が異なるかも知れない点です。
「リスト = 関数」はいいとしても、「タプル = 関数」は言いすぎかも知れません。そこで、タプル = 依存関数〈dependent function〉 とも呼びます。「依存」という言葉を含めると何かトラブルが起きそうで僕は懲りているから(「羹に懲りて膾を吹く」)、「依存」は使いたくないけど、用語「依存関数」は許すとしましょう。
- リスト = 関数 : 普通の関数、値の集合はひとつに決まっている。
- タプル = 依存関数 : 引数〈インデックス〉ごとに値〈成分〉の集合〈型〉が変わってもいい。
まとめ
型ファミリー に対して:
- シグマ型 が定義できる。
- パイ型 が定義できる。
- シグマ型 のインスタンス〈要素〉はタグ付きデータ
- パイ型 のインスタンス〈要素〉はタプル
- タプルを依存関数と呼んでもよい。(あえて呼ぶ必要はないと思うが。)