昨日書いたアレの続き。書いている途中でこれは破滅的にパフォーマンスが落ちるんじゃないかということが気になったので、ちょっくら実験。
import time
x1 = lambda x: x*2
x2 = lambda x: x+2
x3 = lambda x: x-3
t = time.time()
for i in range(1000):
r = i
for f in [x1, x2, x3]:
r = f(r)
print r
print 'loop:',time.time()-t
def _mix(x, y, z):
return y(x(z))
def _c(x, y):
return curry(_mix)(x, y)
t = time.time()
m = reduce(_c, [x1, x2, x3])
for i in range(1000):
r = m(i)
print r
print 'functional:',time.time()-t
すげえ! パフォーマンスが 30% 程度に落ちたよ! いやでもこれは関数本体の処理が簡単だからデメリットが目立つだけで、関数本体の処理に時間がかかればきっと気にならない。いや、でもそこまでして使いたいテクニックじゃねえな、これ。そもそも使う場面が恐ろしく限定されていそうだし。
io の List オブジェクトの append メソッドは add メソッドへと変更になった模様。ってこれ、リファレンスには全然反映されてないんだけど。
io の Regex rSubstitute は明らかにおかしいんじゃねえか?
rSubstitute(s, "(https?://[-.!~*'()a-zA-Z0-9;/?:@&=+$,%#]+)", "<a href=\"\\1\">\\1</a>")
このコードに期待される動作は、 s の中のリンクらしき文字列を全部アンカーに置換するというものだ。ところが、実際にやってみると最後に出現したパターンだけが置換される。これはちょっとまずいな。
そんなこんなで不具合を抱えているけど、一応掲示板が完成した。面倒臭いから Subversion リポジトリでのみ配布 & ドキュメントは一切無し。どうせ大した分量じゃないから、ソース読めばすぐに理解できるよ。というか、リファレンスに載ってない関数とかオブジェクトを探すのにえらい苦労したんですけど。
ていうか io はドキュメンテーションが追い付いていなさ過ぎる。いや、そこらの事情とか気持ちは痛い程良く分かるんだけど。
とりあえず掲示板を作る上で発覚した、公式のオンラインドキュメントに載ってない事をいくつか。
Python のドキュメントと比べちゃいけないのだろうけど、いくらなんでもこれは、ねえ? 一応、使えることは使えるからいいんだけど。
いくらなんでも適当過ぎたなあ、あの掲示板は。最初は Wiki でも作ってやろうかと思ったのだけど (そういや Wiki ってこういう時には格好のネタだよな) 、途中であまりにも io の正規表現がアレなことに気が付いて予定変更。まったく、世話を焼かせるなあ。
それでいろいろと考えてみたのだけれど、次のような書き方で何とかなるようだ。
#!/usr/bin/env ioServer
s := "asv12kfgj56oi1xc"
p := "[0-9]{2}"
r := Regex clone
r setPattern(p)
r setString(s)
m := Map clone
r allMatches foreach(k, v,
m atPut(v, r rSubstitute(v, r pattern, "--"))
)
s replaceMap(m) println
Map オブジェクト (Python でいう辞書) にマッチした文字列と置換後の文字列の組を格納して、 String replaceMap で一括で置き換えるという寸法。面倒くさいね、これ。
ちなみに正規表現内にグループがある場合、 v の値はマッチした文字列全体と各グループのリストで渡される。そのため、 rSubstitute には v at(0) で渡さなければいけない。
ところで replaceMap は Python の文字列にはないので、次のコードみたいな書き方になるな。
for k, v in m.iteritems():
s = s.replace(k, v)
一連のアレに一応決着。何だよこのコード。
f1 = lambda x, y: (x+y, x-y)
f2 = lambda x, y: x*y
f3 = lambda x: x-3
def mix(funcs, args):
if not args.__class__.__name__ in ('tuple', 'list'):
t = []
t.append(args)
args = t
if len(funcs) > 1:
return mix(funcs[1:], funcs[0](*args))
else:
return funcs[0](*args)
m = curry(mix)([f1, f2, f3])
print m((3, 1))
自分で言うのも何だけど、無駄な代入やローカル変数の宣言をしたくないってだけでこんなコードを書くのは本末転倒という他無いね。
ぶっちゃけもう Web アプリは飽き飽きしてるんだけど、かといってデスクトップアプリは俺が使わない。俺がこのサイトでやってるのはあくまでも趣味のプログラミングなんだから、自分が使わないものは作りたくない。いや、作ってはいけない。
本当は作りたいものがあるのだけれど、流石に規模的に大変というか、その前に俺は卒論を終わらせなければいけないのだが。
さて、 Python で妙なコードを書くことがとても楽しい今日この頃、またまたいかれたことを思い付いてしまいました。とりあえずコードを。
@curry
def contract(arg_types, ret_types, f):
def func(*args):
if isinstance(arg_types, tuple):
for arg, dbc in zip(args, arg_types):
if not isinstance(arg, dbc):
raise TypeError(dbc)
else:
if not isinstance(args[0], arg_types):
raise TypeError(arg_types)
r = f(*args)
if isinstance(ret_types, tuple):
for arg, dbc in zip(r, ret_types):
if not isinstance(arg, dbc):
raise TypeError(dbc)
else:
if not isinstance(r, ret_types):
raise TypeError(ret_types)
return r
func.func_name = f.func_name
func.arg_types = arg_types
func.ret_types = ret_types
return func
これが一体何をする関数かと言うと、以下のように関数の引数と戻り値のチェックを行うために使うのだ。
@contract((str, int), str)
def multi_str(x, y):
return x*y
この場合、 multi_str には文字列と整数を渡さなければならない。もしも別の型のものを渡した場合、例外が投げられる。ついでながら、 multi_str.arg_types で引数の型リストの確認が出来るようになっている。
これが何の役に立つかって? 昨日あたりから書いてる関数合成でなら役に立つかも。あとはプログラムの見通しをよくする効果もあるかもしれない。読みやすいと感じる人がいるかどうかは微妙だが。
でもこれ、オブジェクトに適用する場合にはちょっと問題があるかも。第一引数に必ず自身のクラスを渡さなければいけないのだから、ややこしいことになるな。オブジェクトに対して適用するための別物を作るか、それとも内部で処理を分けるかしたほうが良いな。