Ruby
Python
新人プログラマ応援

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

Ruby、Python、JavaScriptなどは動的型付け言語と呼ばれ、『変数』には型が指定されておらず、型は変数に代入される値の方が持っています。1

また、同様に配列/リストや、ハッシュ/辞書/オブジェクトに、異なる型の値を同時に格納できます。

ところで、動的型付け言語について以下のような「利点」を紹介する記事を見かけることがあります。曰く:


  • 配列にどんな型の値も格納できるので便利

  • どのような型の値でも代入できるので、型を意識しなくてもよい

  • 変数に型がないので変更に強い

  • if文で値の型をチェックすれば、変数に型は不要なのです

どれも誤解です。

動的型付け言語を正しく使うには、動的型付け言語が作られ・使われている背景を理解しなければなりません。


動的型付け言語は、未発達な静的型付け言語の代替案に過ぎない(かった)

Ruby・Python・JavaScriptなどが作ら普及した1990 〜 2000年代前後には、ロクな静的型言語がありませんでした。低級で安全に書くのが難しいC言語、複雑怪奇なC++、冗長で遅くジェネリクスを欠いたJavaなどです。

これらの言語で辛いのは変数に型を指定しなければならないこと。まあ、これについては説明するまでもありませんね。

ArrayList<String> alist = new ArrayList<String>(); // 右辺を見れば型は明らかだが、書かなければならない

もう一つの重要な「辛さ」は、インターフェースの指定をクラス側が行うのでダックタイピングをしにくいことです。

例えば、以下のような、ファイルのインターフェース / 具象クラスを定義する 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とか。