Python、Ruby、JavaScriptなどは動的型付け言語と呼ばれています。
動的型付け言語では、変数や関数の引数には型が指定されておらず、値が型を持っています1。また、リスト(配列)や辞書(ハッシュ、オブジェクト)に異なる型の値を混ぜて格納することができます。
ところで、動的型付け言語について以下のような、「利点」を紹介する記事を見かけることがあります。曰く:
- 配列にどんな型の値も格納できるので便利
- どのような型の値でも代入できるので、型を意識しなくてもよい
- 変数に型がないので変更に強い
- if文で値の型をチェックすれば、変数に型は不要
どれも誤解です。
動的型付け言語を正しく使うには、動的型付け言語が作られ・使われている背景を理解しなければなりません。
古い静的型付け言語の辛さ
Ruby・Python・JavaScriptなどが普及した1990年代 〜 2000年代には、ロクな静的型言語がありませんでした。低級で安全に書くのが難しいC言語、複雑怪奇なC++、冗長で遅くジェネリクスを欠いたJavaなどです。
これらの言語で特に辛いのは型名を書かなければならないこと。人間なら一目で型が分かる部分にも型を明示しなければなりません。
ArrayList<String> alist = new ArrayList<String>(); // 右辺を見れば、alistの型は明らかだが、書かなければならない
もう一つの重要な「辛さ」は、インターフェースの指定をクラス側が行うのでダックタイピングをしにくいことです。例えば、以下のような、ファイルのインターフェース / 具象クラスを定義する C++ のコードがあったとしましょう:
// 読み書き可能なファイルを表すインターフェース
class IFile {
public:
vector<char> read(int n);
int write(vector<char> v);
};
// 読み込み可能なファイルの具象クラス
class File: public IFile;
そして、このインターフェースを使う関数を作ったとします。
// ファイルをすべて読み込む関数
vector<char> read_all(IFile f) {
// 当然、.read は使うが、.writeは使わない
}
read_all
が受け付ける引数は IFile
を継承したクラスのオブジェクトのみです。たとえ .read
を提供していたとしても、IFile
を継承していなければアウトです。
また、あるクラスをread_all
の引数に渡したければ IFile
を継承しなければなりませんが、そのためには IFile
のメソッドをすべて定義しなければなりません(read_all
では .read
しか使っていないのに)。
IFile
もFile
もread_all
も全て自分で書いたコードなら回避策もあるのですが、IFile
がライブラリのような自分が手を出せないところで定義されたものだったりすると辛いことになります。
動的型付け言語なら辛くない
そうした暗黒時代において、ほとんどの人々は無力でした。プログラミングとはこういうものだと思い込んでいました。
しかし、
- 未来を発明するパパ
- 子供とパワーレンジャーを観てホンダ・アコードで牛丼屋に連れて行くパパ
- モンティパイソン大好きのパパ
- 学生時代はノートに自作言語を書くのが趣味だったパパ
といった人たちは気づいていたのです。
コンパイル時にインターフェースをチェックをしなければいいじゃないか?
そうです、動的型付け言語だと引数の型自体をチェックしないので、こうした辛さはないのです!!
# Ruby
alist = [] # 型を書かなくてもよい
def read_all(f)
# 引数の型を指定しないので、
# 引数の値が所定のメソッドを提供してくれさえすれば、
# どんな型の値でも動作する
end
※追記: 上記の説明は「1990年代に『動的型付け』というアイデアが考案された」というようにも読める表現になっていますが、実際には動的型付け言語はそれ以前からありました。
動的型付け言語には「インターフェースが無い」のか?
動的型付け言語は、あくまでダメダメな静的型付け言語の辛さを回避するために作れられたものです。型とかインターフェースの概念を否定しているわけではありません。
例えば、動的型言語版の read_all(f)
では、f
は 「.read
メソッドを提供しなければならない」という(仮想的な)インターフェースを実装することを要求しています。.read
メソッドが無いオブジェクトを渡したら、当然エラーになります。実際の開発では「f
は .read
メソッドを提供することを期待する」とコメント等に書いておかなければなりません。
よって、動的型付け言語は「インターフェースが無い」わけではなく
- 目に見えないインターフェースがある
- インターフェースのチェックを、コンパイラーの代わりに人間が行なっている
とも見做せます。
今でも動的型付け言語がベストなの?
静的型付け言語も進化しています。
現在では、多くの言語が変数定義の型を省略できる機能(型推論)を持っています。
またGo言語では「インターフェースを関数の引数の側で指定する」仕様になっています(構造的部分型)。つまり、動的型言語でコメントに書かれていた「f
が .read
メソッドを提供することを期待する」的なことを、コンパイラでチェックできるようになるのです。
また、90年代に比べて開発環境(特にIDE)が進歩し、インターフェースをリファクタリングしたり、アダプタークラスを作ったりすることが容易になりました。そのため、(古い)静的型付け言語であっても、昔ほどの辛さは感じなくなりました。
すると、動的型付け言語の 「チェックを人間が行う」という特徴は昔に比べてデメリットが目立つようになっています。
とは言え、言語を選ぶ判断材料は静的vs.動的だけではありません。ライブラリの充実度とか、利用実績とか、現に使ってしまっているかどうかとか。また、動的型付け言語すなわち「型チェックが無いから危険」というわけでもありません。動的型付け言語には、それぞれエンバグしにくく書くためのイディオムとかデファクトスタンダードのLintツールがあります。プログラミング言語は上手に使って、楽しく書きましょう。