このブログの更新は Twitterアカウント @m_hiyama で通知されます。
Follow @m_hiyama

メールでのご連絡は hiyama{at}chimaira{dot}org まで。

はじめてのメールはスパムと判定されることがあります。最初は、信頼されているドメインから差し障りのない文面を送っていただけると、スパムと判定されにくいと思います。

参照用 記事

レコードの型階層を合理的に説明できるか

レコードデータの型階層(type hierarchy)は、ときおり考えてみる問題なのです。毎回考えた結果を忘れている(苦笑)ような気がするので書き留めておくことにしよう、っと。JavaScriptを事例に考えます。ただし、現状のJavaScriptの型システムは奇妙で不格好なところがあるので、JavaScript 2.0(ECMAScript 第4版)をベースにします。

[追記]id:mal_blueさんより、上に「JavaScript 2.0(ECMAScript 第4版)」として参照したURLは、現在では歴史的意味しか持たないことをご指摘いただきました。(コメント欄参照)。したがって、以下の説明で引用されている型システムが次期JavaScriptにそのままの形で取り入れられるわけではありません。

しかし、次期JavaScript仕様がどうなるかは、このエントリーと引き続くエントリーの内容に直接の影響はないので、この追記以外の修正はしません。次期JavaScriptの仕様そのものに関しては、コメント欄に記述されているURLを参照してください。[/追記]

内容:

  1. 値の型
  2. レコード型
  3. 未定義(undefined)値を使ってみる
  4. それじゃどうする

●値の型

今のJavaScriptでは次のような変なことが起こります。


js> typeof 5
number
js> typeof new Number(5)
object
js> typeof "hello"
string
js> typeof new String("hello")
object
js>

JavaScript 2.0では、このへんは整理され、次のようなデータ型が導入されるようです。

データ型 説明
Never 値はまったく無い
Void 値はundefinedだけ
Null 値はnullだけ
Boolean 値はtrueとfalse
Number +0.0, -0.0, +∞, -∞, NaNを含む数値
String 文字列、nullを含む

参考:

JavaScript 2.0にない概念ですが、上に挙げた型の値をすべて含むような型をAnyValue型としましょう。これらの型を値の集合と解釈するなら、集合の包含関係は次のようになります。


    AnyValue
   / | \\___
  /  |  \   \
Boolean Number String Void
 \   |  | / /
  \  | Null/ /
   \ | // /
    Never__/

●レコード型

JavaScriptでは、{name: "板東トン吉", age: 27}のようなデータをオブジェクトと呼びますが、ここではより一般的な用語を使ってレコードと呼びましょう。nameとageはレコードのフィールド名です。

フィールド名がfoo, barであるようなレコードの全体を Record{foo, bar}と書きましょう。先に例に挙げたレコードは、Record{name, age}に含まれることになります。各フィールドの型を制限したいことも多いので、Record{name: String, age: Number}のような書き方も許します。Record{name: String, age: Number}は、パブリックな型付きフィールド(だけ)を持つクラス宣言だと思ってよいでしょう。あるいは、レコードに対するスキーマとも言えます。フィールド型を書いてないRecord{name, age}は、Record{name: AnyValue, age: AnyValue}とみなすのが妥当でしょう(議論の余地はありますが)。

以下、話を簡単にするため入れ子のレコードは考えません。つまり、フィールドの型は、先に挙げたNever, Void, Null, Boolean, Number, String, AnyValueのどれかに限るとします。

●似ているようでも何の関係もない

データ{name: "板東トン吉", age: 27}は、レコードの集合Record{name: String, age: Number}に入るのは明かです。しかし、データ{name: "板東トン吉", age: 27, married: false}はRecord{name: String, age: Number}に入りません。余計なフィールドmarriedがあるからです。{name: "板東トン吉", age: 27, married : false}は、Record{name: String, age: Number, married: Boolean}になら入ります。

以上のこと整理しておきましょう。

  1. 以下 ton1 = {name:"板東トン吉", age:27} とする。
  2. 同様に、ton2 = {name:"板東トン吉", age:27, married : false} とする。
  3. Person1 = Record{name: String, age: Number} とする。
  4. Person2 = Record{name: String, age: Number, married: Boolean} とする。

このとき:

  1. ton1 ∈Person1
  2. ton2 !∈Person1 (!∈ は「所属しない」こと)
  3. ton2 ∈Person2

なんとなく、Person2 ⊆ Person1 のような気がするかもしれませんが、そんなことはありません。もし、Person2 ⊆ Person1 なら、ton2 ∈Person2 から ton2 ∈Person1 のはずですが、上の事実が反例を与えています。では、逆に Person1 ⊆ Person2 なのかな? それも違います。それどころか、データ集合Person1とPerson2は一切何の関係もありません。事実は、

です。

「えっ? そんなはずはない」と思われるでしょうね、その疑念の背景には、次のような“感覚”があるのでしょう。


// 構文はJava

class Person1 {
public String name;
public Number age;
}

class Person2 extends Person1 {
public Boolean married;
}

上のような“感覚”は自然なのかもしれませんが、この“感覚”をうまく説明するのはけっこう面倒なのです。安直素朴な定式化はすぐに破綻します。

●未定義(undefined)値を使ってみる

ton1 = {name: "板東トン吉", age: 27} にはフィールドmarriedが存在せず、ton2 = {name: "板東トン吉", age: 27, married: false} にはフィールドmarriedが存在するので、この2つのデータはまったく別物になっています。しかし、実際のJavaScriptでは、ton1のフィールドmarriedにアクセスできます。


js> var ton1 = {name:"板東トン吉", age:27}
js> ton1.married == undefined
true
js>

つまり、フィールドmarriedが存在しないのではなくて、存在するが値がundefinedになっているだけ、と考えることができます。marriedの値がundefinedであるようなレコードのスキーマは次のように書けます。

  • Person1' = Record{name: String, age: Number, married: Void}

{name: "板東トン吉", age: 27} ←→ {name: "板東トン吉", age: 27, married: undefined} という対応で、Person1とPerson1'のデータは1:1に対応が取れます。

しかしですね、こうしてみても事情は改善しないのですよ。確かに、Person1とPerson1'は1:1対応で関係付けられますが、Person1'とPerson2はやっぱり何の関係もありません。「明示的に書いてないフィールドの値はundefinedにする」という細工は分かりやすいのですが、整合的型システムの構成には役に立ちません。

●それじゃどうする

直感的には、Person1とPerson2は関係しそうですが、値の集合としては何の関係もない(共通部分は空集合)のです。特殊値undefinedを使う細工も本質的な改善にはなってません。

じゃ、どうやってレコード型(例えばPerson1とPerson2)のあいだに型階層関係を導入したらいいのでしょうか? それは次回に考えます(今日は疲れたので、「続く」)。