Hatena::Groupcoders

ラシウラ出張所 このページをアンテナに追加 RSSフィード

2007/05/19

[] 左結合な中置演算子の作り方  左結合な中置演算子の作り方 - ラシウラ出張所 を含むブックマーク はてなブックマーク -  左結合な中置演算子の作り方 - ラシウラ出張所  左結合な中置演算子の作り方 - ラシウラ出張所 のブックマークコメント

より、無駄を減らして

class infix:
    def __init__(self, func):
        self.func = func
        pass
    def __call__(self, left, right):
        return self.func(left, right)
    def __ror__(self, left):
        return boundleft(lambda right: self.func(left, right))
    pass

class boundleft:
    def __init__(self, func):
        self.func = func
        pass
    def __call__(self, right):
        return self.func(right)
    def __or__(self, right):
        return self.func(right)
    pass

Python-2.5では使い方は以下のようになる。infixが使えるのは二引数で呼び出しできるものでなくてはいけない:

@infix
def mul(a, b):
    return a * b

@infix
def add(a, b):
    return a + b

print 2 |add| 3 |mul| 4 #=> (2 + 3) * 4 = 20

結果から左結合の二項演算子になっていることを確認できる。

旧来の使い方も可能

isa = infix(lambda a, b: a.__class__ == b.__class__)

print [1,2,3] |isa| [] #=> True

右結合は?

Pythonの右結合演算子は、代入関係と**だけになる。まず、代入演算はa += b += cのような連続はパーズエラーになるため、使えない。

唯一**が__pow__や__rpow__で上書きできる。ただし、それを有効にするためにはinstance型は、__coerce__で引数側の型を変換しなくてはいけない。

class infixr:
    def __init__(self, func):
        self.func = func
        pass
    def __call__(self, left, right):
        return self.func(left, right)
    def __pow__(self, right):
        return boundright(lambda left: self.func(left, right.obj))
    def __coerce__(self, other):
        return (self, wrap(other))
    pass

class wrap:
    def __init__(self, obj):
        self.obj = obj

class boundright:
    def __init__(self, func):
        self.func = func
        pass
    def __call__(self, left):
        return self.func(left)
    def __rpow__(self, left):
        return self.func(left.obj)
    def __coerce__(self, other):
        return (self, wrap(other))
    pass

使い方例

@infixr
def comp(f, g):
    return lambda a: f(g(a))

add2 = lambda a : a + 2
mul3 = lambda a : a * 3
add4 = lambda a : a + 4

print (add4 **comp**  mul3 **comp** add2)(1) #=> (1 +2) *3) +4 = 13

実際のところ、右結合演算子が必要な状況はほとんどない。代入、pow演算子、(Rubyのように括弧のいらない)関数適用くらいだが、それは組み込まれている。関数型言語だと、例でも使った関数合成は右結合になる。 foo.bar.buzz はfoo.(bar.buzz)である。関数適用ではfoo (bar (buzz x)))であってほしいからだ。これはpythonにもほしくなるかもしれない。

あと(遅延型)関数型言語リストの結合も右結合になる。これはリスト[a,b,c,...]が意味的に(a,(b,(c,(...,()...))))のように、(","を中置演算子としてみれば)右結合になっているからだろうか。っと思ったが、このリストPythonでいうところのgenerator/iteratorであるため、右結合が自然なのだろう。右結合のappend(alist, append(blist, append(clist, dlist))))は、自然に頭のリストをたどってそれが尽きたら次のappendに入れる。一方、左結合のappend(append(append(append(alist, blist), clist), dlist)は先頭の要素を取り出すにも一番奥のappendにいかなくてはいかなくなる。

pythonリストは左結合の+を使って連結するが、右左どちらでもかまわない。