2011/07/19
動的型のメリットは「決断の遅延」かもしれない
Togetter - 「動的型言語のふわふわ感」 読んでて思ったんだけど。
静的型付けと動的型付けで何が一番違うかって考えてくと、「実行前に済んでること」と「実行時」とをどのくらい分けるか、というところに帰着するかもしれない、と思った。
静的型付けは実行しないでも型エラーをコンパイラが見つけてくれる、というのは、つまり実行する前にプログラムが出来上がっているってことを前提にしているわけだな。程度問題ではあるけれど、実行前のどこかの時点で線を引いて、その時点で分かっている情報の整合性を型という枠組みで保証しましょ、ってわけだから。
「実行する前にプログラムが出来上がっている」なんて当然じゃないか、と思うかもしれないけれど、本当にそうだろうか。
上のtogetterで、「Rubyでは 1 + "hoge" が実行時エラーになるけれど、それは型エラーなんだから実行前に捕まえられた方がいいでしょ」という議論があった。でも、それは型エラーじゃないかもしれない。単に、そういうケースへの対応を実行前にはまだ決めていなかっただけかもしれない。
cl-user(1): (defmethod plus ((x number) (y number)) (+ x y)) #<standard-method plus (number number)> cl-user(2): (plus 2 3) 5 cl-user(3): (plus 1 "hoge") Error: No methods applicable for generic function #<standard-generic-function plus> with args (1 "hoge") of classes (fixnum string) [condition type: program-error] Restart actions (select using :continue): 0: Try calling it again 1: Return to Top Level (an "abort" restart). 2: Abort entirely from this (lisp) process. [1c] cl-user(4): (defmethod plus ((x number) (y string)) (format nil "~a~a" x y)) #<standard-method plus (number string)> [1c] cl-user(5): :continue 0 "1hoge"
(Lispに詳しくない人向けの説明: (plus 1 "hoge") というメソッドは未定義なので実行時エラーで止まったけど、そのエラープロンプトから適切なメソッドを追加して実行を再開したので、ちゃんと呼び出し元に答えが返ってくる。)
もちろんこれは極端な例だ。何でもかんでも、実行エラーが出たらその場で直せばいいじゃん、というわけにはいかない。とりわけ、プログラムの作成者とユーザが別々であることが普通になった現代では。
けれども、「プログラムは実行前に完成しているべきである」という前提を疑ってみるのは無益ではないと思う。
例えばユーザによりカスタマイズ可能なプログラム。ユーザが入手した時点でプログラムは完結しておらず、ユーザの手によって使われながら(そのユーザにとっての)完成形へと徐々に進化してゆく、とみなすこともできる。もちろん、プログラム本体とユーザによるカスタマイズ部分を完全に分けて、前者はコンパイル時にがちがちに検査をかけ、後者を実行時に解釈する、という実装は可能だ。でもその分離は本質的なものだろうか? 単に「コンパイル時と実行時の間に線を引かなければならない」という実装上の都合でそうなっているだけかもしれない。
止まること無く走りつづけて、その時その時の状況に合わせてふるまいを変えてゆく必要があるプログラム、というのもある。これも極端な例としては実行中のサーバにランタイムパッチを当てるなんていうケースがあるが、そこまでアドホックな話でないにせよ、あらかじめ変更を見越して後から開発されてくるモジュールを柔軟に読んだり削除したりするようなアーキテクチャというのは考えられる。オンラインゲームで機能を追加してゆくとか ←実際の現場ではさすがに本番系のモジュールを実行中に入れ替えなんてやってないだろうけれど、それも単に今のプログラムというのがそのような実行時の動的変化を安定して扱えないだけだから、かもしれない。ゲームで言えば、プレイしながら振る舞いを調整したいってのもあるね。これも今は振る舞い部分だけ動的スクリプトにしたりするけど、それも本質的な分離ではなく、実装上の都合かもしれない。
思考の道具として、あるいは実行可能なアイディアとしてプログラムを見ることもできる。黒板にメモして、あちこち書き換えながらアイディアを発展させてゆくように、プログラムをあちこち書き換えながらアイディアを詰めてゆく。黒板との違いは、そのプログラムは動かせるということだ。そして、動かしている最中に新しいアイディアを得て、その場で書き換えて(最初から再実行するのではなく)実行を続行したい、というケースは十分考えられる。シミュレーションのように抱える状態が大きいものであれば特にそうだろう。もちろん、ポータブルな形で状態をダンプできるようにしといて、停止→ダンプ→書き換え、再コンパイル→リストア→続行、というワークフローをとることはできる。が、それはやっぱり実装の都合だ。
これらの例をもって、だから静的型付けはだめだ、と主張するつもりはない。現代のプログラムの圧倒的多数は、実行前にプログラムが閉じて完成していることを要請されているだろうし。上に挙げた例でもいちいち注記したように、アーキテクチャを工夫することでフェーズを分離し、静的型付け言語で実装することは可能だ。もちろん、可能だからといってそうすべきであるということにはならない。
動的型付けを好むプログラマが一定数いるのは、プログラマが触っている「開発途中のプログラム」というのがまさに「完成前に実行したい」ものだから、なのかもしれない。とりわけ、「何を、どう作るべきか」がまだ見えない状態でとりあえずデータをいじりながら発想を得たい、なんて時には。
Haskellerと話しているとまず型から考えるみたいで、ある意味「こうあるべき/こうあって欲しい」というイデアから演繹的にコードを導き出してるようにも見える。勝手な思い込みだけど。一方、手元にぐにゃぐにゃした不定形のデータがあってそいつをこね回したいって時には、最初に手をつける時に構造や意味を決めすぎないようにしないと話が進まない。動的型付けは、「この操作はこういう意味を持つ」と定義する決断を先延ばしにしていると言えるかもしれない。先に構造と意味を決めてからコードを書くのではなく、動くコードを書いてからその意味を考えて構造をいじる。こう書くとエンジニアリングとしてはかなり危なっかしい感じがするけれど、一般的に「なにかをつくる過程」だと思えばそれほど不自然な話ではない。
とはいえ、Lispプログラムだって実行前にわかってることはたくさんあるんで、わかってる範囲ではコンパイラに働いて欲しいんだな。動かしてこねこねしているうちに形が定まってきたら、そこでアノテーションを入れて静的検査を強化できたらなあと思うことはよくある。
Tags: Programming, Lisp, Haskell
Rui (2011/07/19 20:27:55):
mattn (2011/07/20 01:15:16):
osiire (2011/07/20 03:25:20):
nobsun (2011/07/20 03:39:12):
shiro (2011/07/20 04:45:12):
nobsun (2011/07/20 13:45:25):
shiro (2011/07/20 16:42:34):
kinaba (2011/07/20 22:45:20):
Bak. (2016/11/14 04:10:04):