2019/05/11

性懲りもなくPythonでLispのcondを考える

リニューアルしました

実はまだ考えていた「Pythonでcond」
今回のcondは以下のようなものになりました。


from typing import List
import operator as op 


# Usable as a value of TestForm.checker
CHECKER_DICT = {
        "eq": op.eq,
        "==": op.eq,
        "ne": op.ne,
        "!=": op.ne,
        "<=": op.le,
        ">=": op.ge,
        "<": op.lt,
        ">": op.gt,
        "is": op.is_,
        "not": op.is_not
        }


class TestForm:

    def __init__(self, checker, lhs, rhs, after):
        """ Create test-form

        :param checker:  CHECKER_DICT keys
            e.g.) '==', '<=', 'is' etc...
        :param lhs:  Left hand side
        :param rhs:  Right hand side
        :param after:  Processing performed when the result is True.
        """
        self.checker = checker
        self.lhs = lhs
        self.rhs = rhs
        self.after = after


class TestFormError(Exception):

    def __init__(self, value):
        self.value = value

    def __str__(self):
        return repr(self.value)


def cond(conditions: List[TestForm]):
    """
    e.g.)
    fizzbuzz = lambda x: cond([
        TestForm('==', x % 15, 0, 'FizzBuzz'),
        TestForm('==', x % 5, 0, 'Buzz'),
        TestForm('==', x % 3, 0, 'Fizz')
    ])

    for i in range(1, 31):
        if fizzbuzz(i) is None:
            print(i)
        else:
            print(fizzbuzz(i))
    """
    for c in conditions:
        if c.checker in CHECKER_DICT:
            if CHECKER_DICT[c.checker](c.lhs, c.rhs) is True:
                return c.after
                break
        else:
            raise TestFormError(f"Can't use {c.checker}")


最初TestFormcollections.namedtupleで実装してみたのですが、そうするとtypingでの型アノテーションの部分で、List[TestForm]とはできないので、より明確な型指定ができるクラスに変更しました。と言ってもPythonではガチガチの型指定はできないので、あくまで使い方のヒントみたいなものになります。
使う際はcondのdocstringにあるように、lambdaを活用する感じになります。上記の場合FizzBuzzを例にしておりますが、例えば以下のようなことも可能です。

>>> from cond import *
>>> def hello():
...     return "Hello"
... 
>>> cond([
...     TestForm("==", hello(), "Hello", "This is hello func."),
...     TestForm("==", hello(), "Holle", "This is not hello func"),
... ])
'This is hello func.'

使う上での制約として、TestForm.checkerは予め定めたCHECKER_DICTの中に含まれるものしか使えないようにしています。もし含まれていないものを使用した場合は、TestFormErrorとなります。