web-dev-qa-db-ja.com

Pythonで代数的データ型を定義する最良の方法は?

PythonはHaskellでもOcamlでもないことは知っていますが、Python(2または3)で代数的データ型を定義する最良の方法はどれですか?ありがとう!

31
user1234299

Macropy 代数的データ型、パターンマッチングなどを提供します!

17
user1234299

In Python変数はすでに複数のインスタンスを持つことができます(もちろん同時にではありません)。

>>> x = 5 
>>> type(x)
<type 'int'>
>>> x = ["you", "me", "them"]
>>> type(x)
<type 'list'> 

たとえば、コードで次のことができます。

def f(x):

    if isinstance(x, int):
        pass
    Elif isinstance(x, float):
        pass
    else:
        raise TypeError

Haskellの近くに滞在したい場合は、このようなことを行うことができます。 Haskellであなたが持っていると言う

data Item = Person String Int String | Car String Bool

Python 3.6であなたは書く

def g(x):
    tag, *values = x

    if tag == 'Person':
        name, age, e_mail_address = values

        # do something
        pass
    Elif tag == 'Car':    
        brand, is_diesel = values

        # do something
        pass
    else:
        raise TypeError

Haskellでは「合計タイプ」とも呼ばれます。

別の方法は、クラスを使用することです。何が起こっているのかをより明確にします。たとえば、HaskellのEither

data Either a b = Left a | Right b

In Python Either Int Floatそれは次のようなものになります

class Either:

    def __init__(self, a=None, b=None):
        if (a is None) and (b is not None):                 
            self._left  = None
            self._right = float(b) 
        Elif (a is not None) and (b is None): 
            self._left  = int(a)
            self._right = None
        else:
            raise TypeError 

    @property
    def is_left(self): 
        return self._left is not None

    @property
    def is_right(self):
        return self._right is not None

    @property 
    def value(self):
        if self.is_left:
            return self._left
        Elif self.is_right:
            return self._right 

    def __eq__(self, other):
        if isinstance(other, Either):
            if self.is_left == other.is_left:
                return self.value == other.value 
            else:
                return False   
        else:
            raise TypeError 

    def __str__(self):
        return str(self.value)
0
Elmex80s

これは、比較的Python的な方法での合計型の実装です。

import attr


@attr.s(frozen=True)
class CombineMode(object):
    kind = attr.ib(type=str)
    params = attr.ib(factory=list)

    def match(self, expected_kind, f):
        if self.kind == expected_kind:
            return f(*self.params)
        else:
            return None

    @classmethod
    def join(cls):
        return cls("join")

    @classmethod
    def select(cls, column: str):
        return cls("select", params=[column])

インタプリタをクラックして開くと、おなじみの動作が表示されます。

>>> CombineMode.join()
CombineMode(kind='join_by_entity', params=[])

>>> CombineMode.select('a') == CombineMode.select('b')
False

>>> CombineMode.select('a') == CombineMode.select('a')
True

>>> CombineMode.select('foo').match('select', print)
foo

注:@attr.sデコレータは attrsライブラリ から取得され、__init____repr__、および__eq__を実装しますが、オブジェクトもフリーズします。実装サイズを削減するために含めましたが、広く入手可能で非常に安定しています。

合計タイプは、タグ付き共用体と呼ばれることもあります。ここでは、kindメンバーを使用してタグを実装しました。追加のバリアントごとのパラメーターは、リストを介して実装されます。真のPythonの方法では、これは入力側と出力側でダックタイピングされますが、内部で厳密に強制されるわけではありません。

基本的なパターンマッチングを行うmatch関数も含めました。型安全性はダックタイピングによっても実装されます。渡されたラムダの関数シグネチャが、照合しようとしている実際のバリアントと一致しない場合、TypeErrorが発生します。

これらの合計タイプは、製品タイプ(listまたはTuple)と組み合わせることができ、代数的データ型に必要な多くの重要な機能を保持します。

問題

これは、バリアントのセットを厳密に制約するものではありません。

0
kelloti