動的型付き言語と静的型付き言語

ここ数日、変数に型がないということの利点について考える - PerlならサンプルコードPerl入門 を発端として静的型付き言語と動的型付き言語の話題が盛んになっています。個人的にも、このトピックについていろいろググってみたので、考えの整理的な意味も含めて何か書いてみます*1。尚、この記事は元記事に対してどうこうと言った事は特に意図していません。

前提として、私がある程度「使える」と言うプログラミング言語C++C#Ruby となります(C++この辺C#この辺)。Ruby に関しては趣味で触っているだけなので(プロジェクトっぽいものとしては、SoGap のバックエンドを書いたくらい)、知識・立ち位置としては静的型付き言語寄りと言う事になります。関数型言語は Web 上で盛り上がっているトピックを追う位なので、ほとんど分かりません。

動的型付き言語のメリットとデメリット

この類の話を考える上で参考になりそうなものと考えたところ、まず「Ruby 作者のまつもとゆきひろさんはどう考えているのだろう?」と言う考えに行き着きました。そこで、簡単にググってみると、2006 年とやや古いですが、動的言語のメリットとデメリットについて書かれた(今回の話題に関して参考になりそうな)記事が見つかりました(まつもとゆきひろ コードの未来 辺りを読むと、もう少し最近の面白い事が書かれていたりするのでしょうか)。

ここでは、動的言語のメリットとして簡潔な記述、あるいは柔軟な表現方法と言ったものが挙げられていました。逆に(動的言語の)デメリットとしては、エラー検出と実行効率(実行速度)、そしてコードを読む側になった際の情報量低下と言った点が挙げられていました。火元となった記事でも大いに盛り上がっていましたが、これらの中で最も焦点の当たるものが柔軟な表現力とエラー検出のトレードオフだろうと思います。

コンパイルユニットテスト

かつて、マリー・アントワネットも「コンパイルがないならテストをすればいいじゃない」と言ったように*2、このトレードオフについてコンパイルによるエラー検出機能よりも柔軟な表現を選ぶようになった(選べるようになった)一因として、テスト駆動開発 (TDD:Test Driven Development)、あるいはユニットテストと呼ばれる考え方(開発方法)への認知や議論も進み、補助ツールも充実してきた事が挙げられます。

簡潔性、エラー検出、実行効率など、こんなにもデメリットがあるのに、最近、動的言語が注目されているのはなぜなのでしょうか。もちろんこれらのデメリットがなくなったわけではありません。それでも以前とは状況が変わり、これらのデメリットが与えるダメージが比較的小さくなったとは言えると思います。

…(中略)…

エラー検出についても動きがありました。テスト駆動開発(TDD:Test Driven Develop-ment)の台頭です。個々のモジュールごとにユニットテスト単体テスト)を用意し、頻繁にテストします。場合によってはテストを先に記述し、そのテストを満たすようにソフトウェアを開発するのです。正しくユニットテストが行われていれば、エラーが検出できるはずです。またユニットテストを実施するということは、個々のモジュールの「正しい動作」を検証できる仕様が存在することでもあります。そのぶん、信頼性が向上します。

実際に実行してみないと型不整合エラーが発覚しない動的言語ですが、ユニットテストが正しく行われていれば、本番前に網羅的なテストで検出できるので重大な問題になりません。また、型不整合によるエラーはすべてのエラーのうちもっとも検出が簡単で、直すことも容易なエラーです。TDDでは、もっと検出しにくいロジックそのものの間違いも見つけ出せます。TDDは動的言語だけに適用される方法論ではありませんが、動的言語の弱点をカバーできる働きがあります。

技術の広場 - まつもとゆきひろのプログラミング言語論(4):ITpro

理屈的には、「コンパイルも一種のテストであるため、ユニットテストがきちんと書けているならばコンパイルと言う段階はなくても良い」と言う事ができます。一方で、現実問題としてはコンパイル時に自動的に行っていたテストをプログラマ自らが記述する事になるため、(テストコードの)コード記述量は静的型付け言語のそれに比べてどうしても増加する傾向にあります。

結果として、この問題は「元のコードで楽をできる程度・分量とテストコードで余計に頑張るそれとを天秤にかけている」とも見ることができます。テストコードの記述量がどの程度増えるかについては、人によって感覚や認識にかなり違いがあるようで、大して変わらないと言う主張から恐ろしく増加すると言う意見まで様々のようです(参考:僕がRubyをやめたわけ - yvsu pron. yas)。

変更に対する柔軟性

元記事で、動的型付き言語のメリットとして「変更に強い」と言った点が挙げられていました。これに対して、「静的型付き言語の方が変更に強い」と言うコメントもぽつぽつ見かけられ、どちらが「変更に強い」かと言う点についても論争が巻き起こっていました。この論争については、http://blog.practical-scheme.net/shiro/20130227-equibillium で深く掘り下げて論じられていましたが、これを読んでいるうちに、動的型付き言語の言う「変更に強い」とは「変更に対する柔軟性がある」と言う意味だろうかと思うようになりました。

動的な型が提供するのは、ただ単に「宣言を書かなくてよい」というレベルのものではありません。型宣言は、「この変数には必ずこの型のオブジェクトが入る」という制限です。こうした制限は、将来の変化に対して制約となる可能性があります。動的型は型を指定しないことで、将来の変更に柔軟に適応できる可能性を広げていると考えられます。

…(中略)…

プログラム開発者が型宣言としてたくさんの情報を提供する静的型は、エラーを早く確実に検出できるメリットがあります。その代わりに、型を設計した時の前提が変化すると、たくさん指定した情報をすべて一貫性を保つように更新しなければなりません。動的型は最初から型の指定をしていませんから、こうした変化に強い傾向があります。

技術の広場 - まつもとゆきひろのプログラミング言語論(2):ITpro

この類の「変更に対する柔軟性」については、静的型付き言語においても検討される事があります。例えば C# では、オブジェクト間で List オブジェクトのやり取りを行う(メソッドの返り値にする等)場合、List クラス自体ではなくインターフェースである IList、あるいは ICollection の使用(記述)を推奨される事があります。これは、コレクション(コンテナ)に対して、細かな要求の変化が発生した場合に柔軟に対応できるようにしておくと言う意味があります。

もう少し一般化された例を考えると、Abstract Factory パターン と呼ばれる デザインパターン においても、この類の柔軟性が(柔軟性も)考慮されています。これは、各クラスのインスタンス化を直接行う代わりに特定の関数に任せる事によって実際の生成処理を隠蔽・抽象化し、要求の変更が発生した際に関数の呼び出し側コードの修正を要求する事なく柔軟に対応できるようにすると言うものです。

Abstract Factoryでは、「ある抽象的な型の実装を返す」というインターフェースを定義し、 そのインターフェースの実装を多種揃えるといった形になります。

GoF本では例としてlook&feelを取り上げています。 JavaのswingのライブラリではUIのデザインをごっそり切り替えることができますが、 その具象クラスの生成を具象型を知らずに行いたいというようなケースを想定すればよいのでしょう。

GoF本では適用可能性に以下をあげています。

Abstract Factory パターンは、以下のような場合に有効である。

  • システムを部品の生成、組み合わせ、表現の方法から独立にすべき場合。
  • 部品の集合が複数存在して、その中の1つを選んでシステムを構築する場合。
  • 一群の関連する部品を常に使用しなければならないように設計する場合。
  • 部品のクラスライブラリを提供する際に、インターフェースだけを公開して、実装は非公開にしたい場合。

look&feelは2番目の条項ですね。look&feelの例からすると、多数のクラス群をごっそり切り替える場合にAbstract Factory パターンを適用するのが分かります。

Abstract Factoryパターン と Builderパターンの違い

動的型付き言語で言われる「変更に強い(変更に対して柔軟である)」とは、こう言った類の事をもう少し(明示的にインターフェースを定義して継承させていくと言った手順を踏まずに)手軽にやっていく事を指しているのだろうと思います。

ダックタイピングとインターフェース、総称型

変更に対する柔軟性とも絡んでくる話題ですが、静的型付き言語で(将来的な変更を予想した上で)ある程度の抽象的なプログラミングを行う際に、「インターフェースを定義して、オブジェクト間のやり取りはそのインターフェースを用いて行う」と言った手法がしばしば取られるようになりました。

これに対して、動的型付き言語の界隈では「クラスの継承関係よりも振る舞い(必要なメソッドを持っているかどうか)を重視する」と言う「ダックタイピング」と呼ばれる考え方が広く浸透するようになりました。

動的型が持つこのような柔軟性を表現する言葉として、最近「Duck Typing」という言葉が使われています。これは以下のような西洋の歌の歌詞に由来するものです。

If it walks like a duck and quacks like a duck, it must be a duck.(アヒルのように歩き、アヒルのように鳴くものはアヒルに違いない)

ここから、「アヒルのように振る舞うものは、その実体がなんであってもアヒルと見なす」というルールを引き出すことができます。あるオブジェクトがどのクラスに所属するオブジェクトであるかは一切考慮せず、どのように振る舞うか(どのようなメソッドを持つか)だけに関心を払うのがDuck Typingです。これを言い出したのは「達人プログラマ」として知られるDave Thomas氏です。

技術の広場 - まつもとゆきひろのプログラミング言語論(2):ITpro

静的型付き言語のインターフェースによるプログラミングにおいても、そのインターフェースの多くは「要求する振る舞い(メソッド)を定義する」と言う作業を行っている(と考えられる)ので、やろうとしている事の方向性自体はそんなに変わらないように感じます。しかし、静的型付き言語の場合は継承関係がないクラスではキャスト等による変換ができないため、どうしても「インターフェースの定義と継承(して実装)」と言う手順を省略する事ができません。この辺りが、元記事の筆者が主張する「動的型付き言語の方がコード記述量が少ない」等のメリットに繋がっていくだろうと思います。

こう言った状況において、静的型付き言語においても進化が見られる事となりました。一足早く C++テンプレートと言う名前で総称型が導入されると、JavaC# もバージョンアップの際にジェネリクスと言う名前で総称型が追加されました。

いま、このようにhumanがtouchすると、おのおのの動物が鳴くソースを書いてみる。duck(アヒル)はhuman(人間)に触られると「ガーガー」と鳴き、dog (犬)はhuman(人間)に触れると「ワンワン」と鳴くとする。

第127回 Ruby vs Java ダックタイピングとインタフェースで見る多態性 - bingo_nakanishiの他言語出身者のためのPerl入門

まず,前置きとして上記の記事の課題(ダックタイピング)を継承やインターフェースを用いずに実現するコードを記述してみます.

…(中略)…

template <class Animal> と言う記述以外は Ruby とほとんど同じ形で記述する事ができました.

静的型付け言語におけるダックタイピング (Type Erasure) - Life like a clown

総称型と言う概念が浸透する事によって、「クラスの継承関係よりも振る舞い(必要なメソッドを持っているかどうか)を重視する」と言うダックタイピング(っぽい事?)がコンパイル時チェックの恩恵を受けた状態で利用可能となりました。

尚、蛇足として、インターフェースを定義すると言う事は、要求されているメソッドに関するドキュメントをソースコードとして残せると考える事もできるので、総称型で対応可能な例でもわざわざインターフェースを定義する意義はそれなりにあるだろうとは思います(大人数による共同開発時とか)。

言語の進化と差異の希薄化

先ほど「進化」と言う言葉を使いましたが、多くのプログラミング言語は、自分と異なる言語から良いところを学び、アレンジして取り入れる事で進化を続けています。例えば、「変数に型がないということの利点について考える」の問題について考える - ぐるぐる~型推論による例(反論)が挙げられていますが、(手続き型言語である C++C# においても)C#C# 3.0 にて var キーワードで、C++C++11 にて遂に auto キーワード型推論が導入されました。これらのものが一般的な関数型言語型推論と機能的にどの程度異なるか等は私にははっきりとは分からないのですが、少なくとも変数宣言時に型の記述を省略する事はこれらの言語でも概ね可能となりました。

コード表現の柔軟さ等に目を向けてみても、多くの静的型付き言語がラムダ式等を導入する事によってこれまでよりも随分と柔軟な記述方法を提供しています。一方で、動的型付き言語の方に目を向けると、例えばブラウザベンダの JavaScript エンジンの変態的な精力的なパフォーマンス改善によって、デメリットとして挙げられる事の多いパフォーマンスの問題等も解決しようと進化を続けています。

そう言ったお互いの良いところを取り入れて進化し合うにつれて*3、コアな機能部分に関しては言語間の差異が徐々に薄れていき、言語を選ぶポイントとしては、サードパーティの提供するライブラリ/フレームワークがどの分野に強いかや、自分または共同で開発している人達がどの言語に慣れ親しんでいるか等の周辺環境の事情により強く引っ張られていくのかもしれません。

*1:そう言えば、プログラミングの話題に関してまともな何かを書くのは久しぶりです。

*2:言ってません。

*3:パフォーマンス云々の話は少しずれていますが。