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

一意型の実装

前回は、関数的に入出力を行うプログラムを記述する方法として、モナドとは別のアプローチである一意型について説明しました。今回は、一意型の実装方法について検討します。
一意型は静的型システムを前提とした仕組みであるため、動的型システムを採用するPythonのような言語でそのまま実装することはできません。しかし、それと同等の効果をもつ仕組みは、効率を犠牲にすれば、動的型システムでも実現することができます。つまり、一意型エラーを実行時に検知して、例外として通知するのです。次のプログラムはその方針に従って実装した一意オブジェクトです。

def get (x):
    if isinstance(x, Thunk):
        x = x.__get__()
    return x

class Thunk:
    def __init__ (self, fun):
        self.fun = fun
    def __get__ (self):
        if self.fun:
            self.value = get(self.fun())
            self.fun = False
        return self.value

def fromUnique (x):
    x = get(x)
    if isinstance(x, Unique):
        return get(x.__get__())
    else:
        raise UniqueError()

class UniqueError (Exception):
    pass

class Unique:
    def __init__ (self, obj):
        self.unused = True
        self.obj = obj
    def __get__ (self):
        if self.unused:
            self.unused = False
            return get(self.obj)
        else:
            raise UniqueError()

一度しかアクセスを許さないリソースを作成するには、そのリソースをUniqueオブジェクトでラップします。Uniqueオブジェクトからリソースを取得するにはfromUnique()関数を使います。fromUnique()関数は、最初の呼び出しだけリソースを返し、2回目以降の呼び出しではUniqueError例外を発生させます。
これを用いて入出力関数を定義します。

def fopen (filename, mode, w):
    _w = fromUnique(w)
    f = open(filename, mode)
    return Unique(f), Unique(_w)

def fwrites (text, f):
    _f = fromUnique(f)
    _f.write(text)
    return Unique(_f)

def fclose (f, w):
    _f = fromUnique(f)
    _w = fromUnique(w)
    _f.close()
    return Unique(_w)

最初に、渡された一意オブジェクトが2回目以降のアクセスではないことを確認して、リソースを取り出します。そして、そのリソースを用いて入出力処理を行って、最後に再びリソースを新しい一意オブジェクトでラップして返します。
これを利用して'Hello World!'を出力するプログラムを次に示します。

def helloworld (w):
    f, w = fopen ('output.txt', 'w', w)
    f = fwrites ('Hello ', f)
    f = fwrites ('World!\n', f)
    w = fclose (f, w)
    return w

def main (w):
    w = helloworld(w)
    return w

main(Unique(1))

このプログラムを次のように変更すると、UniqueError例外が発生して、不正なプログラムであることが検知できます。

def helloworld (w):
    f0, w = fopen ('output.txt', 'w', w)
    f = fwrites ('Hello ', f0)
    f = fwrites ('World!\n', f0)
    w = fclose (f0, w)
    return w