1 2 3 >> |
CleanやHaskellといった純粋関数プログラミング言語は生産性が高いとあちこちに書いてあったので、いろいろと勉強しているところだ。 勉強していると、Pythonなどの最近の命令型(imperative)あるいは手続き型(procedural)といわれる言語でもかなり似たような記述ができそうな気がした(さすがにJavaやCとかだと無理そうだが)。そこで、Pythonを使って関数プログラミングの醍醐味をどこまで味わえるかチャレンジしてみることにした。あまり関数プログラミングに馴染みのない人にも理解しやすく説明できるように心がけるつもりだ(というか私もあまり馴染みがない)。 純粋関数プログラミング言語では、遅延評価を行えることが特徴となっている。遅延評価とは、すごく簡単に言ってしまうと、必要になるまで式の評価や文の実行をしないことだ。たとえば、無限に続く数列(数値のリスト)を生成する関数を考えてみよう。Pythonで書こうとすると次のようになるが、実際にはこの関数が値を返すことはない。
幸いPythonにはジェネレータがあるので、関数nums()を次のように書くことができる。
このようにジェネレータを使うと、ある程度遅延評価を行うことができる。 しかし、ジェネレータを直接使うのは、ちょっと都合が悪い場合がある。次の要素を先読みしたい場合などである。 そこで、Pythonのリストデータ型ではなく一方向リストを使ったConsのリスト導入することにする。 ConsはLispで有名な次のようなデータ構造だ。CleanやHaskellでリストといえば、このデータ構造を指す。
Pythonのリストと混同しないように、便宜上、上記のデータ構造をCleanやHaskellに於いてもConsと呼ぶことにする。 このデータ構造、図を見てわかるように先読みをすることはできる。ただし、左側のノードが右側のノードを参照するため、右側のノードが確定しなければ左側のノードを作成することができない。これだと、最後の要素が決まるまで先頭の要素を返せないので、たとえば無限に続く数列をConsで表すことができず遅延評価にも役立たない。 そこで、次のノードを参照しようとしたときにそのノード値を決定するような怠惰なConsを導入することにしよう。次のようなクラス構成とリストとなる。
メソッドCons.lazyCons()やメソッドLazyCons.next()に注目してほしい。メソッドLazyCons.next()は、Consの次のノードが必要になった時点でそのノードを生成し返却するという振る舞いとなる。このように、ジェネレータ以外にも、クロージャ(あるいは関数、ラムダ式)によっても遅延評価を実現できる。 上記のコードには、Pythonのリスト(正確にはイテレータまたはジェネレータ)をConsに変換する関数Cons.toCons()が含まれているので、次のようなコードで動作を確認できる。関数infinite()は無限に現在時刻を生成するジェネレータなので普通に考えるとConsに変換できないが、上記のLazyConsのおかげでConsに変換できる。下記のプログラムでは、実際は先頭のConsのノードしか生成されない。
続いて次のコードを追加すると、Consはトータルで2つ生成される。
以上で、遅延評価を行うために、Pythonジェネレータとクロージャが使えること、エンドレスのデータを表すデータ構造としてのConsの準備ができた。 次回は、これらを使って関数プログラミングらしい実装をしてみる。 |