LoginSignup
5
5

More than 5 years have passed since last update.

Pythonでlet式を使う

Last updated at Posted at 2013-11-25

パッケージ化しました -> hachibeeDI/letexpr

ネタです

PythonでStateモナド
の記事を書いていて、PythonでもHaskellみたいなlet式が欲しいなって思ったので考えてみました。

そしたら、関連する投稿に
letを作ってLet's 1行プログラミング
というものがあったので、流用していい感じにします。
と思ったのですが、ざっと見た感じこれはapplyっぽいので流用は断念しました。なのでがんばります。

let式は、let内で束縛を行い、inで式を評価するようです。
また、let内で束縛した変数はlet式全体から参照することが出来るみたいです。

とりあえず結果を変数的な何かに束縛して、inで使えるようにしましょう

class let(object):
    def __init__(self, action=None):
        if action is None:
            name, act = action
            self.lets = {name: act()}
        else:
            self.lets = {}

    def __or__(self, action):
        ''' :type action: (str, func) '''
        name, f = action
        self.lets[name] = f()
        return self

    def in_(self, func):
        return func(**self.lets)

if __name__ == '__main__':
    x = let(('xx', lambda : 1 + 2)) | ('y', lambda : 'yyy') | ('z', lambda : 5)
    print x.in_(lambda xx, y, z: str(xx) + y + str(z))

  
一応形になりました。
ですが、このままだとlet式同士での値の参照が出来ないのでいい感じにしてやります。

上記のように、Pythonでは関数の引数に対して**辞書を渡すことで、keyに対応する引数にvalueを渡すことが出来ますが、いくつかの評判の良い言語などとは違い引数の過不足に対してはエラーを吐いてきます。
つまり、self.lets_内に保存している結果の中で、各々が必要としている値のみを渡す必要があるわけです。

  

こういうときinspectモジュールが便利なんですよ、ということでgetargspec関数を使って必要な引数のみ渡してやります。

from inspect import getargspec


class let(object):
    def __init__(self, action=None):
        if action is not None:
            name, act = action
            self.lets = {name: act()}
        else:
            self.lets = {}

    def __or__(self, action):
        ''' :type action: (str, func) '''
        name, f = action
        require_arg_keys = getargspec(f).args
        self.lets[name] = f(
            **self.__extract_require_args(require_arg_keys)
        )
        return self

    def __extract_require_args(self, arg_keys):
        return {k: v for k, v in self.lets.iteritems() if k in arg_keys}

    def in_(self, func):
        require_arg_keys = getargspec(func).args
        return func(
            **self.__extract_require_args(require_arg_keys)
        )

  

では、実際に色々な引数を要求する式を渡して試してみます。
インデントとかもカッコよくしましょう。

    measure = \
        (let()
            | ('x', lambda : 10)
            | ('y', lambda : 20)
            | ('size', lambda x, y: x * y)
            | ('hoge', lambda x, y: 'fooo')
        ) \
        .in_(lambda x, y, size:
             'x = {x}, y = {y}, x * y = {size}'.format(x=x, y=y, size=size))
    print measure  # => x = 10, y = 20, x * y = 200

やりましたね。
現状だと、上から下に評価していってるので、下にあるラムダから上の値を取得することは出来ませんが、頑張って式の評価を遅延させたりすればそれも出来るようになりそうですね。
もっとも普通に書いた方が普通に読めるので普通が一番だと思いました。

5
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
5