2024/06/23

Pythonで型のグループみたいなものを作ってみる

型のグループ

Pythonでは、特殊メソッド(__eq__, __lt__, __gt__など)を実装することによって、比較演算子を使うことができるようになります。例えば、intはdir(int)とすると、intの属性が表示されますが、その中に上記の特殊メソッドも含まれており、比較演算子を使うことができるようになっています。

Pythonにはint、strなど型はあります。しかし、Pythonには「この特殊メソッドを実装しているものは〇〇というグループである」というようなものはありません。

なので、作ってみました。

Eq

まず、Eqという__eq__を実装しているグループを作成してみます。


from abc import ABC, abstractmethod


class Eq(ABC):
    @staticmethod
    @abstractmethod
    def __eq__(x, y):
        return x == y

特殊メソッド__eq__を実装しているものをEqグループに属しているものとします。

そのために上記のコードにさらに以下のものを追加します。この__subclasshook__を実装すると、__eq__を持っているものはEqのサブクラスと認定され、issubclass関数にてTrueとなります。しかし、これを実装しないと、もともと__eq__を持っている組み込み型のintやstrなどが本当はEqグループに属せるのにissubclass関数ではFalseとなってしまいます。


    @classmethod
    def __subclasshook__(cls, C):
        if cls is Eq:
            if "__eq__" in C.__dict__:
                return True
            return NotImplemented

次にEqのグループに属するものを作成します。


class Int:
    def __init__(self, val):
        self.val == val

    def __eq__(self, other):
        return Eq.__eq__(self.val, other.val)

ここでclass Int(Eq)のような抽象クラスEqを継承するパターンと、registerというメソッドを使い、仮想的サブクラス扱いにするパターンがあります。

今回は後者の方でやっていきます。というのは、予定としてはRustの#[derive(Eq)]のような形で使えたらいいなと考えているからです。そのためには以下のような関数を作成し、デコレータを使えるようにします。


def derive(*abscls):
    def derive_wrapper(cls):
        def wrapped(abscls):
            return abscls.register(cls)
        for a in abscls:
            return wrapped(a)
    return derive_wrapper

これで、以下のような形で使うことができるようになります。


@derive(Eq)
class Int:
    ...

Eq以外でも例えば、Ord(<, >などの比較演算子を持つ)なんていうグループもあっても良いだろうし、OCamlのBaseのようにSexpableというS式との変換ができるグループもあっても良いと思います。もし、複数のグループに属する感じにするなら以下のように


@derive(Eq, Ord, Sexpable)
class Int:
    ...

という書き、それぞれ必須のメソッドを実装すればOK。組み込み型も足りない必須メソッドを追加すればうまくいくはず...