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

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

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

参照用 記事

JavaScript配列とJSON配列、ルーズとタイト

最近、JavaScriptJSON圏論モナドベックの分配法則)に関わる面白い現象を見つけました。圏論のところは置いといて、今日はJavaScriptJSONの具体的な話だけをします。

内容:

  1. JavaScriptのルーズな配列
  2. 配列とオブジェクトの統合
  3. JavaScriptJSONの違い
  4. ルーズとタイト

JavaScriptのルーズな配列

JavaScriptでは、[,1,,3] のような配列を認めます。単に構文的に余分なカンマを許すだけではなくて、このカンマがデータ表現としての意味をちゃんと持つのです。Firebugで試してみましょう。


>>> var x = [,1,,3]
>>> x.length
4
>>> x[0] // 何も表示されない
>>> x[0] === undefined
true
>>> x[1]
1
>>> x[2]
>>> x[2] === undefined
true
>>> x[3]
3
>>> x[4] === undefined
true

第0項目、第2項目にはundefinedが入っていたのです。この配列の長さは4なので、インデックスが4以上の場所にもundefinedが入っていると考えます。

ちなみに、undefinedはJavaScriptの大域変数でリテラルではありません。未定義値(というちゃんと定義された値)に対するリテラルはありません。(void 0) という書き方がリテラルの代用になります。undefined や (void 0) の型はundefined型です。


>>> typeof undefined
"undefined"
>>> (void 0) === undefined
true
>>> undefined = 0 // こんな事やってはいけない!
0
>>> typeof undefined
"number"
>>> (void 0) === undefined
false

僕は個人的に、[,1,,3]のような配列を「歯抜け配列」と呼んでいました。ですが、「歯抜け」って呼び方もヒドイのでルーズ配列と呼ぶことにします。

去年(2009年)の夏頃の次男=歯抜け:

配列とオブジェクトの統合

JSONでは、ルーズ配列を許しません。また、JSON配列とJSONオブジェクトはまったく違ったデータ構造で、JSON配列の項目は整数インデックスでアクセスされ、JSONオブジェクトのプロパティは文字列キーでアクセスされます。(「もう一度、ちゃんとJSON入門」参照)

一方、JavaScriptは配列とオブジェクトの区別があまりハッキリしません。{1:1, 3:3, length:4} というオブジェクトは、先のルーズ配列と区別が付きません。


>>> var x = {1:1, 3:3, length:4}
>>> x.length
4
>>> x[0] // 何も表示されない
>>> x[0] === undefined
true
>>> x[1]
1
>>> x[2]
>>> x[2] === undefined
true
>>> x[3]
3
>>> x[4] === undefined
true

振る舞いとしては、配列と非負整数をキーとしたオブジェクは同じです。もっとも、項目を削除したり追加してもlengthがメンテナンスされないあたりで馬脚を現しますが。


>>> delete x[3]
true
>>> x[3] === undefined
true
>>> x.length
4
>>> x[5] = 5
5
>>> x.length
4

lengthを考えなければ、JavaScriptの配列は特殊なオブジェクトだと言ってもいいでしょう。「特殊」なところは、プロパティのキーが非負整数に限定されることです。

JavaScriptJSONの違い

JSONではルーズ配列を認めていません。また、JSONオブジェクトのキー(メンバー名、プロパティ名)は文字列だけです。整数値のオブジェクト・キーなんてモノはないので、「配列は特殊なオブジェクト」とは言えません。

それと、JSONスカラー型にはundefinedが入ってないのです。基本となるJSONスカラー型は number, string, boolean, null です。integerはnumberの部分型となっています。複合型として配列型とオブジェクト型があり、配列とオブジェクトは全然別なものです(配列がオブジェクトに包含されるわけではない)。

JavaScriptでは、未定義な配列項目や未定義なオブジェクト・プロパティにアクセスしても、未定義値を返せばいいので、特に大騒ぎするようなことではありません。しかしJSONでは、未定義値がないので、未定義項目/プロパティの「値を返す」ことは不可能です。やれることは、例外を発生させることだけです。

ルーズ配列や未定義値を許すか/許さないかは、一概に良し悪しを判断できません。一長一短です。僕自身は、JSONの型システムのほうが厳密で安全だと思い、JavaScript流のルーズさは避けようと思ってきました。しかし最近、ルーズ配列や未定義がないと苦しい感じがしてきたのです。

ルーズとタイト

JavaScriptのように、未定義値とルーズ配列を認める態度をルーズポリシー、そんなものは認めないとするのをタイトポリシーと呼びましょう。ルーズポリシーとタイトポリシーは相反するようですが、僕としては二者択一はしたくない。両方使いたい、何とか折り合いがつかないかと思っていました。

冒頭に書いたように、モナドベックの分配法則を使うと、ルーズポリシーとタイトポリシーの関係をハッキリと記述できます。「またオオゲサな」と思うかも知れませんが、オオゲサじゃない方法では解決できなかったのですよね。このオオゲサな話はタブンそのうちします。