なんか、趣味では最近はC言語ばっかりだったりするわけですが。
さて、関数型言語系をカジった人なら誰しも取り付かれる、モノ、それがパターンマッチ。パターンマッチが使えると、とにかく直感的にコードをかけますよね。
つーわけで、Pythonでパターンマッチを実装してみました。機能的には
- リスト,タプルに対するパターンマッチ
- パターン変数への束縛
- ガード条件
- 任意のオブジェクトに対するパターンマッチ
- 部分パターンの束縛(Ocamlのas)
あたりを実装してみました。これだけあれば、かなり便利にコードをかけます。できるだけ、手軽に書けるように工夫してみました。こんな感じです。
変数束縛とガード。getattrでごにょごにょしてるので簡単にかけます。
m = Match([1,2,3])if m.when([1,2,m.var]) and m.var > 2:print m.var# >> 3
こう使えば、Pythonに念願のswitchが!
m = Match(10)if m(9):print 1elif m(10):print 2else:False# >> 2
部分パターンを束縛してみます。[1,2,m.var]全体をallというパターン変数に束縛します。
m = Match([1,2,3])if m.when([1,2,m.var]) and m.var > 5:Falseelif m.when(m._as_all([1,2,m.var])):print m.allprint m.varelse:raise StandardError("")# >> [1, 2, 3]# >> 3
任意のオブジェクトにも使えます。いわゆるレコードに対するマッチも簡単にできるということです。
class Test(object):def __init__(self, v1, v2):self.v1 = v1self.v2 = v2def __repr__(self):return "Test(%s, %s)"%(repr(self.v1), repr(self.v2))m = Match([1, Test(2, 3)])if m.when([1, m._class(Test, {"v1":2, "v2": m.v2})]):print m.v2else:False# >> 3
オブジェクトに対するパターンマッチは__match__メソッドを定義するとカスタマイズできます。ここらのアイデアはScalaからいただきました。
class Test2(Test):def __match__(self):return {"value": self.v1 + self.v2}m = Match([1, 2, Test2(3,4)])if m.when([1,2, m._class(Test2, {"value": m.var})]):m.varelse:False# >> 7
結構いい感じな気がします。
ダウンロード
実装のお話
ソースコードはこんな感じ。
class Match(object):class _var(str): passclass _class(object):def __init__(self, klass, attrs):self.klass= klassself.attrs= sorted(attrs.iteritems())def match(self, m, obj):props = getattr(obj, "__match__", lambda: obj.__dict__)()return issubclass(obj.__class__, self.klass) and \m.when(self.attrs, sorted(props.iteritems()))class _as(object):def __init__(self, name, pattern = None):self.name = nameself.pattern = patterndef __call__(self, pattern):self.pattern = patternreturn selfdef __init__(self, obj):self.obj = objself.bind = {}def __getitem__(self, key):if not self.bind.has_key(key):if key.startswith("_as_"):return self._as(self._var(key[4:]))return self._var(key)return self.bind[key]__getattr__ = __getitem____call__ = lambda self, *a, **k : self.when(*a, **k)def when(self, pattern, obj = None):if not obj: obj = self.objif isinstance(pattern, (self._var, self._class, self._as)):if isinstance(obj, (list, tuple)):pattern = [pattern]obj = [obj]if not isinstance(obj, (list, tuple)) and \not isinstance(pattern, (list, tuple)) :obj = [obj]pattern = [pattern]if not isinstance(obj, (list, tuple)) or \not isinstance(pattern, (list, tuple)) :self.bind = {}return Falseif len(obj) != len(pattern):if not ((pattern[-1].__class__ == self._var) and pattern[-1].startswith("__")):self.bind = {}return Falsefor i, (value, pat) in enumerate(zip(obj, pattern)):if value == pat:continueelif pat.__class__ == self._var and pat.startswith("__"):self.bind[str(pat)] = obj[i:]return Trueelif pat.__class__ == self._var:self.bind[str(pat)] = valueelif pat.__class__ == self._class:if not pat.match(self, value):self.bind ={}return Falseelif pat.__class__ == self._as:if not self.when(pat.pattern, value):self.bind ={}return Falseself.bind[str(pat.name)] = valueelif isinstance(value, (list, tuple)) and isinstance(pat, (list,tuple)):if not self.when(pat, value):self.bind = {}return Falseelse:self.bind = {}return Falsereturn True
まぁわりかしシンプルですね。
今年も終わりが近づいてまいりました。年をとると時間がすぎるのが速いナァ・・・と痛感しております。