前回(その2)から引用:
“未定義の定義”ごとに、型や型階層の概念が少しずつ違ってきます。それらは並列に存在する異なった定式化であり、優劣を論じても生産的ではありません。無理に優劣を付けようとすると、不毛な議論になるわけです。
というわけで、「未定義とは何か」を中心に話を進めます。
ton1 = {name: "板東トン吉", age: 27} のとき、ton1.marriedは“未定義”です。この状況の取り扱い方として、次のような処方が考えられます。
- ton1.married という式は構文エラーとして前もって排除する。
- ton1.married はどんな値にもなり得ると考える。
- ton1.married はどんな値でもないと考える。
- ton1.married はある特別な値であると考える。
1番目の考え方は話題にしません。残りの3つを扱います。4番目は現状のJavaScriptの方法と似てはいますが、後でJavaScriptとは異なった発想を紹介します。
これらの“未定義の定義”を導入して比較する際に、インスタンス/スキーマ/一般レコードという道具立てがあると便利なので、その説明を先にします。
内容:
- レコードのインスタンスとスキーマ
- インスタンスとスキーマの統合:一般レコード
- インスタンス/スキーマの一般レコードへの埋め込み
- 一般レコードの包摂順序
- 埋め込みと包摂順序を使ってみれば
- もう少し述べるべきこと
●レコードのインスタンスとスキーマ
今までレコードデータと言ってきたものを、ときに(レコードの)インスタンスとも呼びます。{name: "板東トン吉", age: 27}や{name: "板東トン吉", age: 27, married: false}はインスタンスですね。それに対して、{name: String, age: Number}のような形を(レコードの)スキーマと呼びましょう。インスタンスとスキーマは形がソックリですが、次の点が異なります。
既に使っている記法 Record{name: String, age: Number}は、スキーマ{name: String, age: Number}に適合するレコードの集合ということになります。(スキーマに適合する集合はイッパありますが、とりあえす気にする必要はありません。)
●インスタンスとスキーマの統合:一般レコード
レコードのインスタンス{name:"板東トン吉", age:27} と、レコードの集合/型を定義するスキーマ{name: String, age: Number}は形が似ています。いっそのこと、インスタンスもスキーマも両方含むような広い世界を考えてみてはどうでしょうか。
試みに、次のような抽象的(実装を伴わない)データ構造を考えます。
- 任意のフィールド名に対して“値”が対応している。(全域性)
- フィールド名に対する“値”は単一値ではくて値の集合である。(集合値)
このようなデータを仮に一般レコード(universal record)と呼びましょう。形式的に定義すれば、フィールド名として許される名前の全体をNameとして、Name→Pow(AnyValue) という写像が一般レコードです。ここで、AnyValueはすべての値の集合、Pow(X)はXのベキ集合(Xの部分集合全体の集合)です。
任意の一般レコードを書き下すのは(フィールドも値も、ものすごくたくさんあるので)困難ですが、我々が興味のあるのは、少数*1のフィールド以外は一定値をとるものなので、例えば次のように表記できます。
- {name: String, age: {21, 27}, married:Boolean, *: {undefined}}
最後に付いた星印「*」が「その他のフィールド」を意味します。この例では、name, age, married以外のフィールドの値はすべて{undefined}です。
レコードのインスタンス、スキーマ、そして今導入した一般レコードはよく似ています。が、同じではありません。インスタンスとスキーマを一般レコードの世界に埋め込んで考えたいのですが、この埋め込みは色々ありえます。埋め込みの仕方で後の議論も変わってきます。
●インスタンス/スキーマの一般レコードへの埋め込み
インスタンスとスキーマを一般レコードの領域に埋め込む方法で、一番扱いやすいのは次の方法(ルール)でしょう。(「扱いやすい」とは僕の印象で、絶対的な判断ではありません。)
インスタンスの埋め込み:
- 単一の値は、その値だけを持つ単元集合に直す。
- 明示的に書いてないフィールドの値はNever(空集合{})とする。
スキーマの埋め込み:
- 明示的に書いてないフィールドの値はAnyValueだとする。
具体的な事例を挙げれば:
- {name:"板東トン吉", age: 27} → {name:{"板東トン吉"}, age: {27}, *: Never}
- {name:"板東トン吉", age: 27, married: false} → {name:{"板東トン吉"}, age: {27}, married: {false}, *: Never}
- {name: String, age: Number} → {name: String, age: Number, *: AnyValue}
- {name: String, age: Number, married: Boolean} → {name: String, age: Number, married: Boolean, *: AnyValue}
- {name: String, age: Number, married: Void} → {name: String, age: Number, married: Void, *: AnyValue}
まずはこの方法、つまり、未定義のフィールドの値は「インスタンスではNever、スキーマではAnyValu」とするルールを調べてみます。
●一般レコードの包摂順序
xとyが一般レコードであるとき、ある種の順序(記号「⊆:」で表す)を次のように定義します。
- x ⊆: y とは、どんなフィールド名aに対しても、x.a ⊆ y.a なこと
以下に補足的説明。
- 記号「⊆」は、集合の包含関係です。
- 一般レコードでは、フィールド値は集合なので、「x.a ⊆ y.a」は意味を持ちます。
- 一般レコードは、任意のフィールド名に対する値(集合値)が定義されているので、「どんなフィールド名aに対しても」という表現も有効です。
- 記号「⊆:」で表現される関係は集合の包含関係と似てますが、同じではないので違う記号(コロンが付いている)にしました。
「⊆」と「⊆:」を区別したのと同じ理由で、上のように定義された一般レコードにおける順序関係を包摂<ほうせつ>順序と呼ぶことにします。一般レコードとその上の包摂順序は非常に便利です。所属関係(∈)や包含関係(⊆)では説明できない現象を包摂順序(⊆:)を使うとうまく取り扱えます。
●埋め込みと包摂順序を使ってみれば
インスタンスton1とton2、スキーマPeson1とPerson2を次のものだとしましょう。
- ton1 = {name: "板東トン吉", age: 27}
- ton2 = {name: "板東トン吉", age: 27, married: false}
- Person1 = {name: String, age: Number}
- Person2 = {name: String, age: Number, married: Boolean}
Person1やPerson2をレコードの集合と解釈すると:
- ton1 !∈ Person2 (「!∈」は「所属しない」)
- ton2 !∈ Person1
でした。この事実は、レコードを「公開フィールドだけのクラス」とみなしたいときには、たいへん具合の悪い結果です。
さて、インスタンス/スキーマを先に述べた方法で一般レコードとみなしたものを「'」を付けて示しましょう。つまり、
- ton1' = {name: {"板東トン吉"}, age: {27}, *: Never}
- ton2' = {name: {"板東トン吉"}, age: {27}, married: false, *: Never}
- Person1' = {name: String, age: Number, *: AnyValue}
- Person2' = {name: String, age: Number, married: Boolean, *: AnyValue}
表に書いたほうがわかりやすいかな。
データ | name | age | married | その他 |
---|---|---|---|---|
ton1' | {"板東トン吉"} | {27} | Never | Never |
ton2' | {"板東トン吉"} | {27} | {false} | Never |
Person1' | String | Number | AnyValue | AnyValue |
Person2' | String | Number | Boolean | AnyValue |
この表を眺めて、⊆: の定義を参照すれば、次のことがわかります。
- ton1' ⊆: Person2'
- ton2' ⊆: Person1'
これは望ましい結果です。さらに、次の関係も容易にわかります。
- ton1' ⊆: ton2' ⊆: Person1' ⊆: Person2'
一般レコードの世界では、インスタンスもスキーマも、すべての関係を包摂関係⊆:で捉えることができます。
●もう少し述べるべきこと
インスタンスもスキーマも一般レコードの世界に埋め込めて、そこでは一様な扱いができます。しかし注意していただきたいのは、インスタンスとスキーマが完全に同じ扱いにはなっていないことです。例えば、中身のない形 {} はインスタンスともスキーマとも見なせますが、インスタンスとして埋め込めば{*:Never}と解釈され、スキーマとして埋め込めば{*:AnyValue}と解釈されます。
インスタンスは外延(個体とその集まり)、スキーマは内包(条件、制約)なので、鏡に映った像のように「似てはいるけど逆である」ような関係になっています。一般レコードの世界は、実体と鏡像が共に(対等な権利で)棲める世界なわけです。
さて、インスタンス/スキーマを一般レコードの世界へ埋め込む方法は他にもあります。公平さや相対化のために、他の方法も検討するべきだと思うので、(たぶん)あと1回「続く」。
*1:Nameは通常無限集合です。ここで「少数」とは有限個を意図しています。