メッセージ。 - 関数型言語と値の集合を意識したプログラミングスタイルについて

# 関数型言語と値の集合を意識したプログラミングスタイルについて

昼になんか思い付いたんだよなぁ。なんだっけかなぁと考えていて、いまさっき思い出した。

HaskellってSQLに似てる。というか、関数型言語がSQLに似てるのかもしれない。というのも、たとえばSQLのSELECT文では、WHERE区で条件を指定して入力の集合を小さい集合に切り出し、その結果をまたSELECT文に渡すという感じでプログラムを組んでいくからだ。

要するに、SELECT文が返してきた「値の集合」は、必ず使用される。使用する主体は、別のSELECT文かもしれないし、ユーザーかもしれないけど、どっちにせよ使用される。一方で、普通の手続き型言語では、式を計算(評価)した結果は、必ずしも「使用」されない。感覚的には、使用する場合が6割で使用しない場合が4割ぐらいかなぁ。

たとえば代入文があるとする。「i = 4」とか。このとき、「i = 4」を実行した結果は、使用されず捨てられる。つまり、i = 4が成功したか成功していないか、「この式がもし値を返すとしたら何が返されるべきか」といったことは、誰も気に留めない。for文やwhile文、if文やcase文なんかも同様に、文を計算した結果は使用されず捨てられる。え? それの何が問題かって? そうさなぁ。

1つ言えることがある。「関数型言語のプログラマは、基本的に計算した式の結果(値)を捨てない」ということだ。捨てないで、できるだけ利用しようとする。括弧を重ねて式の結果に対する処理をどんどん膨らませていけば、自然にプログラムはできあがるからだ。そのようなスタイルを採ると、代入文はいらなくなる。for文やwhile文もいらない。それらは再帰で書けるし、再帰で書くほうが良いコードになる場合も多い。なぜ良いコードになるかというと、そのようなスタイルでコードを書くと、「集合に対する操作を書き下している」という感覚になるからだ。

「集合に対する操作」スタイルでプログラムを書くと、後で処理対象の値(値の型やクラス)を変更したくなった場合に、ロジック構造の変更を最小限にとどめることができる。ロジック構造は同じにしておいて、型だけ変更すればよいからだ。逆に、for文を使ったり、if文の中でelse節を省略したりしてしまうと、プログラムのロジック構造が本質から外れて、ad hoc(場当たり的)になってしまう。要するに本質的には必要のない「if文ばっかりのコード」になってしまうのだ。

if文がたくさんあると、条件漏れによるバグの温床になるし、シンプルさによるパワーが損なわれてしまう。現代では、天動説と地動説のどちらが優れているかと問われれば、ほとんどの人が地動説と答えるだろう。なぜ地動説が天動説より優れているかといえば、地動説のほうがシンプルなルール(法則)で説明しているからだ。天動説が正しいことを説明しようとすると、とてもたくさんのルール(法則)が必要になる。地動説のほうが、より少ないルール(法則)でより広い範囲のことを説明できる。つまり、地動説のほうがシンプルさによるパワーがあるということだ。

--

Haskellの嫌なところ。文字列を行ごとに分割・結合するlines・unlines関数が嫌い。たとえば、「lines "test"」も「lines "test\n"」も、どちらも"test"が返ってくる。違うものを入力して同じ結果が返ってくるというのは、美しくない。集合の写像が一意でなくなったら、可逆な変換が行えなくなってしまう(あとで元通りに戻そうと思ったときにできなくなる)。その結果、プログラミングの力は著しく削がれてしまう。

Schemeで文字列を行ごとに分割しようと思ったら、(string-split "test\n" "\n")を実行することで、"test"と""の2つの文字列が返ってくる。(string-split "test" "\n")の実行結果は"test"だけだ。このようになっていることで、Schemeで文字列を分割したり解析したりするときは、完全に問題集合に集中できる。Haskellのlinesでこれをしようとすると、どうしても「もしも○○だったら」と考えなければならなくなってしまう。

もちろん、プログラミングをしているときに、「"test"も"test\n"もどちらの入力でも"test"を返してほしい」というような関数を使いたい場合はよくある。ただそれは、ユーザーからの入力を受け取る部分だけだ。プログラムの内部においては、集合の写像が一意で可逆に決定される関数(Schemeにおけるstring-splitのようなもの)を使わなければならない。そういう部分こそ、プログラミングのパワーを分ける分水嶺となる。

Haskellにlinesがあることが悪いとは言わないけど、写像を一意に行分割・結合できる関数も用意しておいてくれたらなぁと思う。要は、大が小を兼ねるように(兼ねることがあるように)、写像が一意(可逆)であることは、写像が非可逆であることを兼ねるのだ。

ちなみに、Schemeでlines相当のことをやろうとしたとき、それ専用の関数が用意されていないので、おいらは次のようなコードを書くことにしている。(port->list read-line (current-input-port))。逆に、プログラム内部で文字列を行分割したい場合は、(string-split str "\n")としている。
2008-11-05 02:46:46 / ふじさわ / Comment: 0 / Trackback: 0

Comment

コメント投稿機能は無効化されています。

Trackback

TrackBack投稿機能は無効化されています。