高階関数(クロージャ)とメソッド(クラス)
(書いていたら当たり前な感じのことになった。)
クロージャとオブジェクト
こんな話がある。
あまり厳密な話をする気はないけれど、例えばpythonなどのクラスとクロージャの両方をサポートする言語を使うとき、どちらを使ったら良いか迷うことがあった。
普通にコードを書いている時には一定のガイドラインが頭の中にあるかもしれない。
具体的な話。
例えば以下のような感じ。
引数を受け取りその値を状態として保持する。次に適用する時には、保持した状態との和を返す。
そういう機能を持ったものを作りたい。機能はひとつしかないのでクロージャで書ける。
クロージャで書いたadder
def adder(x): def _adder(y): return x + y return _adder #------------ adr = adder(10) print adr(20) # => 30 print adr(30) # => 40 print adr(-10) # => 0
機能がひとつしかないときはクロージャで十分。ただし、機能が複数になるとクロージャだけでは対応しづらくなる。
例えば以下のような機能を追加したいとき
1. 保持した状態を異なる値に変更したい
2. 和だけではなく、差、積、商などを求めたい。
共通する状態を利用する機能が複数ある時クラス(オブジェクト)を作成した方が楽なことが多い。
クラスで書いたAdder
class Adder(object): def __init__(self, x): self.x = x def add(self, y): return self.x + y def sub(self, y): return self.x - y def mul(self, y): return self.x * y def div(self, y): return self.x / y def update(self, y): return self.x = y # ------------ adr = Adder(10) print adr.add(20) # => 30 adr.edit(20) #保持する状態変更 print adr.add(20) # => 40 ## 名前が良くないけど print adr.sub(20) # => 0 print adr.mul(100) # => 2000 print adr.div(2) # => 10
これは普通正しい。でも、クロージャでもやり辛いだけでやれないことはない。
クロージャで頑張ったadder
def adder(x): state = [x] # pythonのcellオブジェクトのせい def add(y): return state[0] + y def sub(y): return state[0] - y def mul(y): return state[0] * y def div(y): return state[0] / y def edit(y): state[0] = y return dict(add=add, sub=sub, mul=mul, div=div, edit=edit) # ------------ adr = adder(10) print adr["add"](20) # => 30 adr["edit"](20) #保持する状態変更 print adr["add"](20) # => 40 ## 名前が良くないけど print adr["sub"](20) # => 0 print adr["mul"](100) # => 2000 print adr["div"](2) # => 10
ちょっとアクセスの方法が違うけれど、オブジェクトを作ったのと同じことはできている。
アクセスの形式は以下のようなクラスを定義すればまったく同じになる。
アクセスの方法を揃えるためにdictの代わりにObjectLikeを使ったもの
class ObjectLike(dict): """これをdictの代わりに使う。""" __getattr__ = dict.__getitem__ __setattr__ = dict.__setitem__ def adder(x): state = [x] # pythonのcellオブジェクトのせい ## 省略 return ObjectLike(add=add, sub=sub, mul=mul, div=div, edit=edit) # ------------ adr = adder(10) print adr.add(20) # => 30 adr.edit(20) #保持する状態変更 print adr.add(20) # => 40 ## 名前が良くないけど print adr.sub(20) # => 0 print adr.mul(100) # => 2000 print adr.div(2) # => 10
継承も難しくない。
クロージャを利用した方法でもクラスの継承をシミュレートできる。
(今回のは内部でクラスを使ってしまっているけれど)
例えば以下のようなクラス定義を真似する。
BarはFooを継承したクラス。メソッドにbar,fooを持つ。fooは親クラスのFooから継承したもの。
普通にクラスで書いた場合、
クラスで作ったBar
class Foo(object): def foo(self): return "foo" class Bar(Foo): def __init__(self, prefix): self.prefix = prefix def bar(self, prefix=None): return (prefix or self.prefix) + "bar" # ----------------------------- print Bar("bar:").foo() # => "foo" print Bar("bar:").bar() # => "bar:bar" print Bar("bar:").bar("obj:") # => "obj:bar"
クロージャを使ってクラスっぽいものを作る。
クロージャで頑張ったbar
class Body(object): def __init__(self, obj, *supers): list_of_obj = [] for _super in supers: for x in _super.list_of_obj: list_of_obj.append(x) list_of_obj.append(obj) self.list_of_obj = list_of_obj def _find_method(self, name): for obj in self.list_of_obj: if obj.get(name): return obj[name] __getitem__ = _find_method def foo(): return Body({"foo": lambda : "foo"}) def bar(pfx): _pfx = pfx return Body({"bar": lambda pfx=None: (pfx or _pfx) + "bar"}, foo()) # ------------- print bar("bar:")["foo"]() # => "foo" print bar("bar:")["bar"]() # => "bar:bar" print bar("bar:")["bar"]("obj:") # => "obj:bar"