Edited at

動的型付け言語も型を意識して書くべき

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しか使っていないのに)。

IFileFileread_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ツールがあります。プログラミング言語は正しく使って、楽しく書きましょう。





  1. なお、変数どころか値も型を持っていない(ような)実行モデルの言語もあります。Tclとか。