用Python定义代数数据类型的最佳方法?

2024-03-28 08:56:29 发布

您现在位置:Python中文网/ 问答频道 /正文

我知道Python不是Haskell或Ocaml,但是在Python(2或3)中定义代数数据类型的最佳方法是什么?谢谢!


Tags: 方法定义haskell数据类型代数ocaml
3条回答

Macropy提供代数数据类型、模式匹配等!

下面是一个相对Pythonic方式的sum类型的实现。

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.sdecorator来自attrs library,它实现__init____repr____eq__,但它也冻结对象。我加入它是因为它减少了实现的规模,但它也广泛可用,而且相当稳定。

求和类型有时称为标记联合。在这里,我使用了kind成员来实现标记。附加的每变量参数通过列表实现。在真正的Pythonic方式中,这是在输入和输出端输入的duck类型,但没有在内部严格执行。

我还包含了一个match函数,它执行基本模式匹配。类型安全也通过duck类型实现,如果传递的lambda函数签名与您试图匹配的实际变量不一致,则会引发TypeError

这些求和类型可以与产品类型(listtuple)组合,并且仍然保留代数数据类型所需的许多关键功能。

问题

这并不严格限制变量集。

在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

如果你想靠近哈斯克尔,你可以这样做。用哈斯克尔语说

data Item = Person String Int String | Car String Bool

在Python3.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中,它也被称为“sum类型”。

另一种选择是使用类。使事情更清楚。例如Haskell的Either

data Either a b = Left a | Right b

在Python中,它将类似于

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)

相关问题 更多 >