静的型付言語/動的型付言語のメリット/デメリットについて考えてみる

http://d.hatena.ne.jp/perlcodesample/20130227/1361928810
Haskellが好きな人です。
普段0.4M行くらいのPHPコードなんとかしてます。

Haskellerへ

変な事言ってないかチェックお願いします。

動的型付言語のトレードオフ

変数に型が無い事のトレードオフが理解出来ていないと思われる。

件のエントリを見ると、変数に型が付いていない方が圧倒的に有利に見える。そうだったら世の中のソフトウェアは全て動的型付言語を用いるはずだ、そうなっていないのは静的型付言語にもメリットがあるからだ、という論を僕はここではしたくはない。


彼が書いている部分でそのような論を用いている箇所が有る。大規模サイトで実際に動的型付言語を用いている、だから十分に使える(はずだ)という。ここは彼の自分の論(型が付いていない方が有利)から発生していない、少しずるい論調だ。


大規模サイトにおいて動的型付言語を用いている理由は、型がどうのこうの以前のビジネス的な理由の方が大きいと僕は思っている。だから、大規模サイトで採用されているから動的型付言語も大規模で十分使えるしミッションクリティカルな領域でも問題ないと言うのは、僕にはナンセンスに見えるわけだ。


僕の立場を書くと、動的型付言語にメリットはあると思う、しかし構築するシステムの規模やチーム規模が大きくなるにつれてそのメリットよりデメリットが大きくなる。そしてそのメリットとデメリットのが反転する規模の分岐点は(常人が思っているより恐らくは)小さい、というものだ。
具体的には10K行程度。もう少し言うなら、一人で実装する場合は実装内容が人一人の頭の中に全て入る程度の規模。複数人で作業するなら、それら一人一人の頭に入る規模の最小値より、更に小さい。動的型付言語は実装を小さくまとめられる限り、そのメリットを十分に享受出来ると思っている。動的型付言語はコンパクトにまとめるための機能を沢山持っている事が多いので、それらをふんだんに活用して、人のメモリを超えない様にすることが望ましいと思う。しかし解くべき問題がそもそも複雑である場合、本質的にコードも複雑にならざるを得ない場合も多い。そのような大規模/複雑な問題に動的型付言語は向いていないし、使うべきではない。そういう立場をとる。


そして静的型システムは、動的型システムよりもテストの記述を少なくしてくれる。以下ではそれを示そう。

関数の引数と返り値が何か分からない動的型付言語

関数fを考えよう。引数Xに対して返り値Yが返る関数だ。

f :: X -> Y

このとき、関数fに対して網羅的にテストをする場合、そのパターン数は型Xの要素数となる。
XがBoolなら2通り、XがCharなら文字の個数通り。


関数gを考えよう。引数S, Tに対して返り値Uが返る。

g :: S -> T -> U

このとき、関数gに対して網羅的にテストをする場合、そのパターン数は(型Sの要素数)*(型Tの要素数)となる。つまりS, Tが共Boolなら4通り。Sの要素数が3, Tの要素数が4なら12通り。

そのはずだ。静的型付言語ならば。


動的型付言語は言語にもよるかもしれないが、関数の引数には任意の値を渡す事が出来る。よって言語でとれる値の要素数をN_langで表すと、1引数関数ならばN_lang通り、2引数関数ならN_lang^2通り、n引数関数ならN_lang^n通りのパターンが発生する。


N_langという要素数は、IntとDoubleが含まれることから、1引数関数であっても1京通りは下らない。それどころか直積構造であるオブジェクトとかも含まれるので一引数だけでも無量大数(10^68)は突破しているだろう。2引数関数、3引数関数あたりでは膨大すぎて見たくもなくなる数だ。


もしかしたら馬鹿にしていると感じるかもしれない。そんなことはそもそも問題ではない、関数毎に受け入れる値はそれよりもずっと小さいのだからと。「テストを書いて保証すればよい」と。


そう感じるならば、恐らくあなたは既に「型チェック相当のテストを自分で書く」という、静的型付言語だったなら自動化出来る事をわざわざ自分の手を使って実行しまっていることになる。しかも型チェックに比べ物にならない程にお粗末な形で。型システムはそのコストを0にし、尚かつシステム全てで一貫性を保ってくれる。


以上、静的型付言語はテストの一部を担保してくれる。


捕捉するなら、動的型付言語ほどでもないが、静的型付言語でも同様の問題は発生し得る。そうであっても、例えば要素が3つしか無い型をIntで代用するなんて阿呆なことはしないし、Intを用いざるを得ない、もしくはIntをラップした型を用いるしか無いなら、それは言語機能が貧弱なだけで型システムとは無関係だ。また、その問題も型で解決しようという試みがあるため、今後に期待したい。

動的型付言語と大規模開発

前述した「関数の引数と返り値で何が渡されるか分からない、何が返るかわからないために発生するエラーのリスク」は、関数の定義数に比例して増大するリスクであるため、動的型付き言語は大規模開発に向かない。


Nullのような欠損値を持つ言語にも同様の議論が当てはまる。「Nullの存在によって関数からNullが返るかどうかわからないために発生するエラーのリスク」は、関数の定義数に比例して増大するリスクであるため、Nullのある言語は大規模開発に向かない。


前述したけども、大規模Webサイトで動的型付言語が採用されているのは型以前の他の要因が大きいと思っており、彼らはそのコストを支払えるし実際に支払っているのだと思う。大規模開発に向かないという僕の主張は変わらない。

他のプログラマが変に使わない保証、制限の共有

ソフトウェア開発上難しい問題のうち、他の開発者に変なコードを書かせない、というものがある。これは能力が低いからと一概には言えない。静的型付言語では型を見れば何を引数に渡せば分かるが、動的型付言語ではその使い方がはドキュメント(人間が書いた不完全な何か)を頼るか実装(人間が書いた、意図が分かる様に分かり易く書いてないかもしれない何か)を見るか、テスト(人間が書いた、もしかしたら見ても使い方が分からないかもしれない何か)を見なければならない。どれにせよ型を見るよりずっとコストが高い。当然規模が大きくなればなるほどそのコストは増える。


静的型付言語でドキュメントが要らないとかテストが要らないとかいう話をしている訳ではない。
そのどれをとっても、動的型付言語は構造的に静的型付き言語よりもコストが高くなるという話をしている。


型とは本質的に制限であるが、ソフトウェア開発に関しては制限を明確にすること、その制限をチームの開発者とコストゼロで共有出来る事は大きなメリットとなる。理想を言うならば、チーム開発で共有される様々なポリシー/制約は、コンパイルエラー/警告あたりまで持ち上げられる事が出来る(つまりコストゼロで共有出来る)と僕は嬉しい。それは言語の役割かどうかと聞かれると微妙だけども。

型とテスト

純粋な関数に関しては、関数の性質をテスト出来る性質テストが有効だ。
現在QuickCheck, SmallCheck等がある。QuickCheckは他言語にも移植されており、Perlにも有るようだ。
Haskellでは型からテストデータを自動で生成する。

型とは設計である

型とは設計だと思っている*1。しかもコードから乖離することのない、生きた設計だ。
型を見れば問題の切り分けが出来ているかどうか分かる。
型を見れば開発者の意図が分かる。
型を見れば静的な性質の多くが分かる。
型を見てそれらが分からないようなら、それは設計(型)が悪いのだろう、と僕は思っている。
さらに型チェックが通れば設計の一貫性も担保される。
設計の変更時にはコンパイラが何処を直せば良いか全て洗い出してくれる。


静的型付言語では型が煩わしくなるという旨の発言を聞くたびに、僕には「私は設計が出来ません/設計を考えた事がありません」と言っている様にしか聞こえない。

型の煩わしさを低減させる試み

型が一部でとても便利である一方、煩わしいと感じることも実用上は存在する。
その煩わしさを低減させる機能の一部を紹介する。

型推論(Type Inference)

型を書かずとも推論する機能。健全性と完全性があり、健全な型推論を持つコンパイラは、型が付けば正しいプログラムであると保証される。完全な型推論をもつコンパイラでは、正しいプログラムであるならば、型が推定出来ると保証される。ここで「正しいプログラム」とはバグが無いという意味ではなく、stuck状態にならない(progress)かつ、well-typedな項は簡約してもwell-typedである(preservation)という意味。のはず。
詳しくは近日邦訳版が発売されるTAPLを買おう。
型システム入門 −プログラミング言語と型の理論−

Deferring Type Errors

型エラーを実行時まで遅延させる。
つまり型エラーが存在する(型の一貫性がない)としても、取りあえず実行させられる機能。
これで動的型付言語の様にプロトタイピングが出来る様になるはず。すごい。
GHC7.6.1 or later.

Type-Holes

Haskellで以前はundefinedを実装を書かずに型チェックを通すために用いていたりしたが、それをコンパイラが強化してサポートしたもの。というかAgdaでまず実装されたものらしい。
型が分からない箇所にholeと呼ばれるプレイスホルダ'_'を書いておくと、そこに書くべき値の型をコンパイラが教えてくれるとい
う機能。のはず。
GHC7.8(?)

あ、

すみませんHaskell以外のことはよくわかりませんし知っていたら教えて下さい。

*1:少なくともHaskellでは