関数プログラミングのアプローチ (12)

制御構造

前回は、入出力の実行をプログラムの本体から分離することで、入出力を含むプログラムを関数的に記述することができることについて説明し、その方法として、メインルーチンをアクション(入出力関数オブジェクト)のリストとして表現するアプローチを採用しました。今回は、この方法が十分な柔軟性を持っていないことを示し、その代替となるアプローチについて説明します。
アクションをリストで列挙する方式は、制御構造を表現することができません。例えば、コマンドラインのシェルの対話環境を実現することを考えてみます。シェルは、まずプロンプトを表示し、入力を待ちます。入力を受け付けると、それを標準入力から命令を読み込んで、その命令を実行し、結果を標準出力に出力して、ふたたびプロンプトを表示して入力を待ちます。このように無限に繰り返される処理をリストで記述するには、無限の長さのリストを用意する必要があり、入出力を実行する前にプログラムが異常終了します。
また、入力の内容によって分岐するようなプログラムを書くことも容易ではありません。なぜなら、リストは入出力を行う前に内容が確定していなければならないからです。
そこで、より現実的な解は、入出力のアクションを入出力が進むに従って、動的に生成するというアプローチになります。また、スコープ規則を利用して、値の受渡しをより柔軟に行うように変更します。次のプログラムを見てください。

def actions (prog, fname):
    ifile = open(fname, 'r')
    def act1 ():
        line = ifile.readline()
        def act2 ():
            ifile.close()
            return line, None
        return (), act2
    return (), act1

このプログラムは、前回のプログラムをアクションを動的に生成する方法で書き直したものです。このプログラムを実行するには、次のようなトップレベルを作成します。

def runIO (args, actions):
    while actions:
        args, actions = apply(actions, args)
    return args

import sys
print runIO (sys.argv, actions)

このプログラムは、前回のアクションのリストを用いたアプローチと同様に関数的なプログラムであり、かつ、動的に次のアクションを生成するため、ループや分岐などの制御構造を表現することも可能です。例えば、次のように書くとファイルの内容をすべて読み込んでリストにすることができます。

def actions (prog, fname):
    ifile = open(fname, 'r')
    def loop (ret):
        def act1 ():
            line = ifile.readline()
            if line:
                return (), loop(ret + [line])
            else:
                def act2 ():
                    ifile.close()
                    return ret, None
                return (), act2
        return act1
    return (), loop([])