型
型について考え直す。単純に型と言っても色々奥が深い。実装に依存した部分もあるし、概念のみの部分もあるし。とりあえず、型と言って思い付くだけでも
- 変数の型
- 型無し
- 宣言有り
- 宣言無し(型推論)
- データの型
- 型無し
- 宣言有り
- (宣言無し(は今のところ無さそう))
に分けられる。実例を挙げると、
- アセンブラ
- 変数型無し、データ型無し。変数もデータも全てビット列にすぎない。操作自体が「eaxレジスタとebxレジスタを整数とみなして足す」といったように、そのデータをどう扱うかを決める。
- FORTRAN
- 変数型有り(宣言)、データ型無し。変数は型を持っているが、データはあくまでもビット列だ。例えばREAL型の変数にINTEGER型の数値を代入しようとすると自動的に変換が発生する。しかし、関数渡しの際には自動変換が発生せず、ビット列を別々に解釈する*1。
- C
- 変数型有り(宣言)、データ型無し。変数は型を持っているが、データはあくまでもビット列だ。異なる型の変数の間はキャストにより変換する。この変換は単純にビット列を読み変えているだけではなく、型の意味を考慮した変換関数によるものである。
- C++
- 変数型有り(宣言)、データ型部分的に有り。基本型のデータは型を持っていないが、クラス型のデータは型を持っている。キャストはCと同様のものに加え、dynamic_castという引数のデータ型を考慮に入れたキャストがある。関数のオーバーロードは許されている。一般の関数は引数に与えられた変数の型によって決まるが、レシーバがクラス・インスタンスを指すポインタのときに基底クラスでvirtual宣言されたメンバ関数を呼ぶ場合に限りレシーバのデータ型のメンバ関数が呼ばれる。
- java
- 変数型有り(宣言)、データ型部分的に有り。基本型のデータは型を持っていないが、クラス型のデータは型を持っている。基本型同士、クラス型同士のキャストが認められている。基本型同士のキャストは型の意味を考慮した変換関数によるものである。クラス型同士のキャストは型の意味を考慮している(C++のdynamic_cast相当)。またinstanceofなどのデータ型に基づいた演算子が存在する。メソッドのオーバーロードは許されている。インスタンス・メソッドは常にレシーバのデータ型のものが呼ばれる。
- ruby
- 変数型無し、データ型有り。変数の型がないのでキャストはない。データ型の変換メソッドはある。あらゆるメソッドは常にレシーバのデータ型のものが呼ばれる。関数オーバーロードは出来ない。
- python
- rubyと類似。
- lisp系
- 基本的には変数型無し、データ型無し。マクロが強力なのでデータ型をデータ内に持つようなシステムをライブラリとして構築することができる。関数オーバーロード(マルチメソッド)は多重ディスパッチによって解決することが多い。
- Haskell
- 変数型有り(型推論)、データ型無し?。型推論なのでキャストはない。データ型の変換関数はある。型クラスを利用することで関数オーバーロードが出来るらしい。
- OCaml
- 変数型有り(型推論)、データ型有り。型推論なのでキャストはない。データ型の変換関数はある。関数オーバーロードは出来ない。
- javascript
- 変数型無し、データ型も微妙。プロトタイプがデータ型に近いがそのものではない。関数オーバーロードは出来ない。
色々リストアップして判ったこと。
- 型宣言は面倒だが、速度面では有利。
- 型宣言があるとキャストが必要になるが、強引なキャストはとても危ない。
- 型推論と関数オーバーロードは相性が悪い。
- 型はバグを減らすと言うが本当か。
- データ型とは要するにクラスを特徴付ける何かへのポインタにすぎない。
- 多重ディスパッチと単なる分岐との違いはあいまいだ。
*1:これは言語の欠陥だと思う。バグを生む原因にはなっても有効利用は難しいもの。