俺の考えではそこでしか使われない処理であるのなら、関数内関数というのはかなり好ましいものだと思う。けれど、関数のネストって遅かったような。俺はパフォーマンスを最優先にはしていないけれど、明らかにパフォーマンスを向上させることが出来、それがコードの品質に致命傷を与えなければ、それをやらない手は無いだろう。
とりあえず次のようなプログラムで実験。
# 通常の関数
def expression(s):
for i, c in enumerate(s):
if c == ":":
return s[:i], s[i+1:]
def start_paren(_t):
for i, c in enumerate(_t):
if c == "(":
exp, elem = expression(_t[i+1:])
return _t[:i], exp, elem
else:
return _t, None, None
def end_paren(_t):
stack = 1
for i, c in enumerate(_t):
if c == "(":
stack += 1
if c == ")":
stack -= 1
if stack == 0:
return _t[:i], _t[i+1:]
else:
raise Exception("Error.")
def tokenize(text):
tex, exp, elem = start_paren(text)
if exp is None:
return tex
else:
node, text2 = end_paren(elem)
return tex, exp, tokenize(node), text2
# 関数のネスト
def n_tokenize(text):
def n_start_paren(_t):
def n_expression(s):
for i, c in enumerate(s):
if c == ":":
return s[:i], s[i+1:]
for i, c in enumerate(_t):
if c == "(":
exp, elem = n_expression(_t[i+1:])
return _t[:i], exp, elem
else:
return _t, None, None
def n_end_paren(_t):
stack = 1
for i, c in enumerate(_t):
if c == "(":
stack += 1
if c == ")":
stack -= 1
if stack == 0:
return _t[:i], _t[i+1:]
else:
raise Exception("Error.")
tex, exp, elem = n_start_paren(text)
if exp is None:
return tex
else:
node, text2 = n_end_paren(elem)
return tex, exp, n_tokenize(node), text2
上記の関数 tokenize と n_tokenize は、次のような変換を行う。つーか、次のバージョンの Ghost のトークン分割の草案なんだけど。
# 入力文字列
text1.
(each x:
text2.
(if x.a == b:
text3.
)
)
text4.
# 結果 (タプルで返ってくる)
('text1.\n\t', 'each x', ('\n\t\ttext2.\n\t\t', 'if x.a == b', '\n\t\t\ttext3.\n\t\t', '\n\t'), '\ntext4.')
でまあ実行時間を計ってみたら、二つの間で大差が無いことが判明。じゃあ関数のネストでいいじゃん。
寒い。どれぐらい寒いかっつーと、ストーブを点けてても寒いぐらい。俺は寒いのと暑いのとでは前者の方が好きだが、それにも限度って物がある。
それはともかくとして、 俺は Weblog という言葉を日記、メモ書き、個人ニュースなどの情報流しっぱなしジャーマンサイトの総称としてとらえているのだけど、どうも多くの人はトラックバックやコメントの有無で判断しているようだ。 Python のクラスで表現すると次のような感じ (細部は思いっきり省略)。
# 一般的な Weblog のクラス構造
class Weblog:
def accept_trackback
def send_trackback
def accept_comment
それに対して俺は、次のように考える。
# トラックバックに必要なインターフェースを定義
# 具体的な実装はしない
class Trackback:
def accept_trackback
def send_trackback
# コメントに必要なインターフェースを定義
# 具体的な実装はしない
class Comment:
def accept_comment
# Web サイト一般を定義する基底クラス
class Website:
def update
def get_update_period # 更新頻度の取得
# トラックバックもコメントも実装した何か
class SomeCGI(Website, Comment, Trackback):
# トラックバックのみの実装
class OtherCGI(Website, Trackback):
# 通常の掲示板
class SomeBBS(Website, Comment):
# トラックバックを実装した掲示板
class OtherBBS(Website, Comment, Trackback):
つまりトラックバックやコメントの有無で Weblog かどうかを判断しているわけでは無い。 get_update_period メソッドの値と内容から Weblogかどうかを推論している。
もうちょっと別の書き方をすると、
ええと、俺が何を言いたいかっつーと、ぶっちゃけ Weblog なんてのは今まであった何かの同梱再販みたいなもので、それで革新的な何かが起こるはずが無いだろうと。確かに各種ブログツールは情報を垂れ流すための敷居を下げたけど、それだけで何が変わるってわけじゃないだろう。
このあたり、新しい言語が出来たからって革新的なソフトウェアが誕生するわけではないってのと似ているな。少なくとも革新的な何かが出てくるには、自分が使っている道具に対する自覚ってのが必要だと思うし、多分俺を含めて多くの人は、革新性を打ち出せるまでに道具の事を知らないと思う。
Javascript でカリー化。こんなものを見せられたら、 Python でも実装してみたくなるのは当然。というわけでやってみた。
def curry(f):
def _curry(*args):
def __curry(arg, x):
full_arg = []
full_arg.extend(arg)
full_arg.extend(x)
if f.func_code.co_argcount == len(full_arg):
return f(*full_arg)
else:
return curry(f)(*full_arg)
if f.func_code.co_argcount == len(args):
return f(*args)
else:
return lambda *y: __curry(args, y)
return _curry
Python では関数もまたオブジェクトであり、関数オブジェクトは自分の関数名や引数の個数を知っている。おかげでこの手のコードがかなり書きやすい。
因みに上記のコードは、以下の呼び出し全てが適正となる。
def avg(x, y, z):
return (x+y+z)/3
print curry(avg)(3,4,5)
print curry(avg)(3)(4,5)
print curry(avg)(3)(4)(5)
print curry(avg)(3, 4)(5)
print curry(avg)(3)(4)()(5)
それでこれが何かの役に立つかって? 役に立つのだ。例えば、この日記を生成している WoD のコードは、カリー化で改善されることが書いていてわかった。
WoD では一日ごとの日記ファイルを生成する際に、過去に書いた日記ファイル名の一覧と生成したいファイル名を引数として渡している。これは前の日、次の日などを出力するためだ。コードで書くとこうなる。
def make_daily_file(all_file_list, file_name):
# 日記ファイル生成処理
for i in files: # 生成したいファイル
make_daily_file(all_file, i)
これをカリー化すれば、
daily = curry(make_daily_file)(ファイル名一覧を取得する処理)
for i in files:
daily(i)
実際には他にも引数として渡さなければいけないものもあって、かなりコードが混沌としている。そういったことを考えると、この手の手法でコードを整理するのは選択肢として十分有りだろう。
ただまあ、この手のコードは一見すると何やってるのかさっぱりという場合があるわけで、シンプルなコードを良とする俺からするとちょっと導入を躊躇う場面があるのも事実だ。今の WoD のコードはかなりの泥沼だけど、関数プログラミングの手法を応用してどれだけコードの品質があがるのか。
とりあえず試しに導入してみるとするかな。
俺は見積りが大雑把な様を表す際に、釣鐘勘定という言葉を使う場合がある。大雑把差の度合としては、どんぶり勘定が顔見知り同士での適当な金の貸し借りだとしたら、釣鐘勘定はバブル期の金の動き方ぐらい。
試しに unittest モジュールでテストを書いてみた。すげえ面倒臭いけど、いったん書いてしまえば楽なのかもしれない。
ところで unittest モジュールだと、あらかじめパスしていなければならないテストとかの定義が出来ないっぽい。これだとちょっと不便な気がする。自分でテスト用のフレームワークでも書くか?