pythonで関数型してみる: 複数の関数を連鎖させる

functionalというモジュールに面白いものが入っているよう。
$ sudo easy_install functional


>>> from functional import *

composeは二つの関数を組み合わせてくれる。


>>> help(compose)
Help on function compose in module functional:

compose(func_1, func_2)
compose(func_1, func_2) -> function

The function returned by compose is a composition of func_1 and func_2.
That is, compose(func_1, func_2)(5) == func_1(func_2(5))

試しに使ってみる


>>> def f(): return 'oh'
...
>>> f()
'oh'
>>> def g(m): return m + ' hai'
...
>>> g('xx')
'xx hai'
>>> g(f())
'oh hai'
oh haiはlolcatの挨拶だ。本来はアッパーケースであるべきだ。
composeから返ってくるのは関数

>>> compose(g,f)

呼んでみると…


>>> compose(g,f)()
'oh hai'
sum(sequence[, start]) -> value はバイナリオペレータの+を複数のものにapplyして一つにする。composeもバイナリ関数だからsumみたいなことがそうなもんだ。どうやらfold[lr]でいけそう。

>>> foldl(compose, lambda x: x, [g, f])
function composition at 0x1018c08>
>>> foldl(compose, lambda x: x, [g, f])()
'oh hai'
ここでlambda x: xはsumのstart、つまり0にあたる。足しても増えない数字はゼロ。applyしても引数が変らず返ってくるの関数と同じ位置付けだ。
三つでもいける

>>> def h(m): return m+ ' there'
>>> h('oh hai')
'oh hai there'
>>> foldl(compose, lambda x: x, [h, g, f])()
'oh hai there'

よく見たらfunctionaにidなる関数が


>>> id(f)

>>> id(f)()
'oh'
>>> id('xx')
'xx'

identity関数のようだ。これでスッキリする。


>>> foldl(compose, id, [h, g, f])()
'oh hai oh'

one-linerだが便利そうなので関数にまとめておく。


>>> def composem(*fs):
return foldl(compose, id, fs)

>>> composem(h,g,f)()
'oh hai there'



こういうメタ関数はは言語の勉強や頭の体操に見えるかもしれないけど、実用性も十分あると思う。仕事でやるデータ処理はMakefileUnixのパイプで行なっている。最近のマシンはパワフルなので、Hadoopを使わずとも結構な量が処理ができる。Makefileは依存を管理してくれるので、いくつものフェーズを踏んでデータを変換していくのに便利だ。しかし、Makefileとパイプでできたシステムの管理を今時のプログラマに保守させようとしたら嫌われるので、pythonで再実装中。そこで、延々と続くUnixパイプをどうするか。lazyなstream処理をどうPythonで再現しようか。lazyと言えばgeneratorだ。 パイプで連らねたコマンドをpythonのgeneratorに置き換えられないか?
えば:

grep foo

をこのように

def grep_foo(in_stream):
for line in in_stream:
if re.match(r'foo', line):
yield line
そして

cut -f 2


def cut_f2(in_stream):
for line in in_stream:
assert isinstance(line, tuple)
yield line[1] # cutは1ベース、pythonのarrayは0ベース
に置き換える。(当然引数毎に関数を定義するようなことはしないが、ここは例ということで…)

このような「コマンド」を繋ぐときどうするか?shellだと|で繋いで終りだが、pythonだとこんな感じになってしまう


foo(...(cut_f2(grep_foo(sys.stdin.readlines())))...)
不特定数の関数を連鎖させる関数があれば

{不特定数の関数を連鎖させる関数}(foo, .., cut_f2, grep_foo)(sys.stdin.readlines())
とできて素敵だ。上のcomposemでこれをやるというのが目論見だ。さてうまくいくか。