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

时间:2013-04-28 01:15:45

标签: python algebraic-data-types

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

5 个答案:

答案 0 :(得分:17)

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

答案 1 :(得分:2)

typing模块提供Union,它与C不同,是求和类型。您需要使用mypy进行静态类型检查,并且明显缺少模式匹配,但是与元组(产品类型)结合使用时,这是两种常见的代数类型。

from dataclasses import dataclass
from typing import Union


@dataclass
class Point:
    x: float
    y: float


@dataclass
class Circle:
    x: float
    y: float
    r: float


@dataclass
class Rectangle:
    x: float
    y: float
    w: float
    h: float


Shape = Union[Point, Circle, Rectangle]


def print_shape(shape: Shape):
    if isinstance(shape, Point):
        print(f"Point {shape.x} {shape.y}")
    elif isinstance(shape, Circle):
        print(f"Circle {shape.x} {shape.y} {shape.r}")
    elif isinstance(shape, Rectangle):
        print(f"Rectangle {shape.x} {shape.y} {shape.w} {shape.h}")


print_shape(Point(1, 2))
print_shape(Circle(3, 5, 7))
print_shape(Rectangle(11, 13, 17, 19))
# print_shape(4)  # mypy type error

答案 2 :(得分:0)

在Python中,变量已经可以具有多个实例。例如,您可以执行以下操作:

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中,也称为“和类型”。

答案 3 :(得分:0)

这是一种相对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 library,它实现了__init____repr____eq__,但也冻结了该对象。我之所以加入它,是因为它缩减了实现的规模,但是它也广泛可用并且相当稳定。

总和类型有时称为标记联合。在这里,我使用了kind成员来实现标签。其他可变参数通过列表实现。以真正的Python方式,它在输入和输出端都是鸭子式的,但内部没有严格执行。

我还包括一个match函数,该函数执行基本模式匹配。类型安全性也通过鸭子输入来实现,如果传递的lambda的函数签名与您要匹配的实际变体不匹配,则会引发TypeError

这些总和类型可以与乘积类型(listtuple)结合使用,并且仍然保留了代数数据类型所需的许多关键功能。

问题

这并不严格限制变体集。

答案 4 :(得分:0)

我开发了一个简单的库,该库允许基于@dataclases定义带标签的并集,并在此处提供可选字段:

https://pypi.org/project/tagged-dataclasses/