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

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

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

参照用 記事

プログラマのためのJavaScript (2):融通無碍な型システム

JavaScriptは「型のない言語だ」と言われています。これは、変数に型を宣言する必要がない(現状ではできない)ことです。しかし、データには型があります。よって、データの分類体系としての型システムもあるはずです。とはいえ、ホレッ、JavaScriptのことですから、型システムも“どうとでもとれるような(解釈の任意性がある)”曖昧な分類体系ですね。よくいえば融通無碍<ゆうずうむげ>、有り体<ありてい>にいえばエエカンゲンですわ。

●とっかかりはtypeof演算子

[追記]: typeof演算子をtypofと書き間違えていた(で、直した)。こういうのはtypoofと言うのだろう:-) )

typeof演算子(関数ではありません)の引数に、定数リテラル、変数、式(expression)などを指定するとその型(の名前である文字列)を返してくれます。


js> typeof 123
number
js> typeof ("Hello" + "World")
string
js> typeof new Date()
object
js> typeof function(x) {return x * x;}
function
js> typeof [1, 2, 3]
object
js> typeof typeof (2 * 3)
string
js>

typeofが返す種別は、 number, boolean, string, object, function, undefined5つ6つ*1です。

「なーんだ、簡単じゃん、6つの型があるだけ」 -- でもないんです。

●型階層をとりあえず描いてみる

上の6種類では、いくらなんでも粗<あら>っぽすぎるでしょう。それに、typeof演算子が返す結果が、自然であるとも僕には思えないし。

型システムが言語仕様でキチンと定義され、型階層の絵(ツリー状、または網状)が描ける言語もありますが、JavaScriptでは、「これで決まり」という型階層はどうもないようです。で、とりあえず僕の主観で描いたのが下のツリー。つっこみどころはいくらでもあるんですが、その言い訳や説明は後(別な回も含む)でします。


JavaScriptデータ型
+--基本型
+--number型
+--boolean型
+--string型
+--オブジェクト的型
+--object型
+--Null型
+--配列型
+--狭義ブジェクト型
+--標準組み込みオブジェクト型
+--実装依存組み込みオブジェクト型
+--ユーザ定義(カスタム)オブジェクト型
+--function型
+-特殊な型
+--undefined型

typeof演算子に敬意を払って、“6つの種別”はそのままこの型階層に埋め込んでいます。その他の(日本語でラベル付けした)ノードは概念的な分類のために導入したものです。

●おまえ達はコウモリか? JavaScriptのデータよ

※注:コウモリ -- あるときは自分は鳥だと言い、あるときは獣だと主張した、という物語がある。

number型、boolean型、string型を基本型としてまとめることに異論はないでしょう。これらに共通する特徴は、(概念的には)値渡しであり、イミュータブル(変更不可能)なことです。文字列(string)に関しては、実際には参照(ポインタ)渡しをするでしょうが、文字列データを変更する手段がないので、理屈としては、値渡し(常に全バイトイメージがコピーされる)だと理解していいはずです。

文字列は変更不可能(イミュータブル):


js> var s1 = "Hello"
js> var s2 = s1.toUpperCase()
js> s1
Hello
js> s2
HELLO
js> s1[0]
H
js> s1[0] = 'h'
h
js> s1
Hello
js>

さて、ところで、[オブジェクト的型>object型>狭義ブジェクト型>標準組み込みオブジェクト型]をさらに細分すると、そのなかにNumber型、Boolean型、String型ってのが現れます。

string型とString object型:


js> var s1 = "Hello"
js> var s2 = new String("Hello")
js> typeof s1
string
js> typeof s2
object
js> s1 == s2
true
js> (typeof s1) == (typeof s2)
false
js>

リテラルとして書いた文字列はstring型なんですが、コンストラクタ関数Stringで生成したモノはobject型になります。s1とs2は型が違うのです。けど、等値比較演算子==で比較すると、s1とs2は等しいのです。ウーム、味わい深い。

さらに、Stringって関数はコンストラクタ(つまり、newと共に)ではなくて、単なる関数呼び出しもできまして、次のようなことになります。

単なる関数としてのString:


js> var s3 = String(s2)
js> typeof s3
string
js>

String関数の戻り値は、object型ではなくてstring型です。このような現象は、number, booleanについても同様に起こります。

●種明かし:君達はコウモリなんですね

上のような、“普通の”プログラマには不可思議な現象は、ラッパー・オブジェクトにより解釈できます。基本(プリミティブ)型のラッパー・クラスはJavaではお馴染みです。int型に対してInteger型、boolean型に対してBoolean型などです。Javaでは、String型は最初からオブジェクト型(参照型、クラスを持つ)なので特にラッパーを持ちませんけどね(これは重要な違い!)。

コンストラクタ関数Number、Boolean、Stringは、ラッパー・オブジェクトを生成するものです。ラッパー・オブジェクトから値を取り出すにはvalueOf()メソッドを使います。
ラッパー・オブジェクト:


js> var i = new Number(123)
js> typeof i
object
js> i.valueOf()
123
js> typeof i.valueOf()
number
js>

一方、単なる関数としてのNumber、Boolean、Stringはデータの型変換機能を提供します(他の言語のキャストに相当)。
データの型変換:


js> Number("123")
123
js> Number("")
0
js> Number(true)
1
js> Boolean(123)
true
js> Boolean("")
false
js> Boolean("false")
true
js>

さらに、「型が異なっていても値が等しくなる」ことは、JavaScriptでは日常茶飯事<さはんじ>。なぜなら、式や文のそれぞれの場所に“期待される型コンテキスト”があり、そこに期待されないデータがやって来ると、JavaScript勝手に、かなり強引に型変換を実行します(この点はPerlなんかと似てます)。この自動型変換が、演算子==の左と右に対してさえも実行されます。

ゆるい等値性:


js> 123 == "123"
true
js> false == ''
true
js>

もっときびしい等値性判定には===が使えます。この===を使って、==を解釈すると:


js> 123 === "123"
false
js> 123 === Number("123")
true
js> false === ''
false
js> false === Boolean('')
true
js>

いちおうツジツマがあっているでしょ。でも、JavaScriptのバージョンや方言により挙動が変わってしまうみたい(そこがまたJavaScriptの味わい)。

Javaでオートボクシング/アンボクシングが導入されたのは最近(J2SE5)ですが、JavaScriptでは、もっと以前から/もっと暗黙に/もっと強力に(迷惑なくらい強力に)自動型変換をしていたのです。よって、JavaScriptのデータは、あるときは鳥だと言い、あるときは獣だと主張するコウモリのようなモノですね。

●今回のまとめ

  1. typeof演算子は、number, boolean, string, object, function, undefinedのいずれかを返す。
  2. number型、boolean型、string型は、概念的に値渡しでイミュータブルだから基本型と考えてよい。
  3. number型、boolean型、string型に対応するラッパー・オブジェクト型として、参照型であるNumber型、Boolean型、String型がある、と考えてよい。
  4. 関数Number、Boolean、Stringは、ラッパ・オブジェクトのコンストラクトとしても、各基本型への型変換関数としても使える。
  5. JavaScriptは強力な自動型変換機能を持ち、それは、==の両辺に対してさえも実行される。
  6. 結果として、JavaScriptのデータには型があるような/ないような? コウモリ的なモノである。

データ型、型システムについてはもう少し言いたいことがあるので、たぶん、次回もこのネタです。

*1:数を間違えたのは、undefinedは型じゃないという印象があったからです。でも、特殊な型と考えることにします。