python的代数数据类型

algebraic-data-types的Python项目详细描述


adtcircleci

adt是一个库,在python中提供代数数据类型,语法清晰直观,并支持通过amypy plugin输入mypy plugin

目录:

  1. 什么是代数数据类型?
    1. 模式匹配
    2. 与枚举相比
    3. 与继承相比
    4. 其他编程语言中的示例
  2. 安装
    1. mypy插件
  3. 定义ADT
    1. 生成的功能
    2. 定制方法

什么是代数数据类型?

代数数据类型(也称为ADT)是一种表示单个类型的多个变体的方法,每个变体都可以有一些与之相关的数据。这个想法非常类似于标记的联合和和和类型,在python中表示为枚举。

ADT对于各种数据结构都很有用,包括二进制树:

@adtclassTree:EMPTY:CaseLEAF:Case[int]NODE:Case["Tree","Tree"]

抽象语法树(就像您可以作为解析器、编译器或解释器的一部分实现):

@adtclassExpression:LITERAL:Case[float]UNARY_MINUS:Case["Expression"]ADD:Case["Expression","Expression"]MINUS:Case["Expression","Expression"]MULTIPLY:Case["Expression","Expression"]DIVIDE:Case["Expression","Expression"]

或变体类型的更一般的版本,如表示类型A或类型B的类型,但不能同时表示这两种类型:

L=TypeVar('L')R=TypeVar('R')@adtclassEither(Generic[L,R]):LEFT=Case[L]RIGHT=Case[R]

模式匹配

现在,定义一个类型本身并不那么有趣。当您在ADT上进行模式匹配(有时称为"解构")时,ADT的许多表现性就产生了。

例如,我们可以使用上面的adt来实现一种错误处理:

# Defined in some other module, perhapsdefsome_operation()->Either[Exception,int]# Run some_operation, and handle the success or failuredefault_value=5returnsome_operation().match(# In this case, we're going to ignore any exception we receiveleft=lambdaex:default_value,right=lambdaresult:result)

旁白:这与错误处理在语言中的实现方式非常相似,如haskell,因为它避免了引发和捕获异常的不可预测的控制流,并确保呼叫者需要明确地决定在错误情况下要做什么。

对于上面的表达式类型,也可以执行同样的操作(只需要匹配更多的情况):

e:Expressionresult=e.match(literal=lambdan:...,unary_minus=lambdaexpr:...,add=lambdalhs,rhs:...,minus=lambdalhs,rhs:...,multiply=lambdalhs,rhs:...,divide=lambdalhs,rhs:...)

与枚举相比

adt与python标准库中的enums有些相似(事实上,大写的命名约定故意相似)。

例如,枚举版本的表达式可能看起来像:

classExpression(Enum):LITERAL=auto()UNARY_MINUS=auto()ADD=auto()MINUS=auto()MULTIPLY=auto()DIVIDE=auto()

但是,这不允许数据与这些枚举值中的每一个相关联。表达式的特定值将告诉您存在的表达式的类型,但表达式的操作数仍必须存储在其他视图中这里,

从这个角度来看,adt类似于enums,可以有选择地将数据与每个案例关联起来。

与继承相比

代数数据类型是面向对象编程语言的一个相对较新的介绍,原因很简单,继承可以复制相同的行为。

继续我们的示例,使用表达式adt,下面介绍如何用python中的继承来表示它:

classExpression(ABC):passclassLiteralExpression(Expression):def__init__(self,value:float):passclassUnaryMinusExpression(Expression):def__init__(self,inner:Expression):passclassAddExpression(Expression):def__init__(self,lhs:Expression,rhs:Expression):passclassMinusExpression(Expression):def__init__(self,lhs:Expression,rhs:Expression):passclassMultiplyExpression(Expression):def__init__(self,lhs:Expression,rhs:Expression):passclassDivideExpression(Expression):def__init__(self,lhs:Expression,rhs:Expression):pass

这显然更加冗长,使用这些类型的代码也变得更加复杂:

e:Expressionifisinstance(e,LiteralExpression):result=# do something with e.valueelifisinstance(e,UnaryMinusExpression):result=# do something with e.innerelifisinstance(e,AddExpression):result=# do something with e.lhs and e.rhselifisinstance(e,MinusExpression):result=# do something with e.lhs and e.rhselifisinstance(e,MultiplyExpression):result=# do something with e.lhs and e.rhselifisinstance(e,DivideExpression):result=# do something with e.lhs and e.rhselse:raiseValueError(f'Unexpected type of expression: {e}')

ADT提供了一种简单的方法来定义一种类型,它是一组可能的案例中的一个,并且允许数据与每个案例关联,并与之打包/解包。

其他编程语言中的示例

代数数据类型在函数式编程语言中非常常见,如haskell或scala,但它们也越来越被"主流"编程语言所接受。

下面是几个例子。

生锈

rustenums实际上是成熟的adt。以下是如何定义ADT的方法:

enumEither<L,R>{Left(L),Right(R),}

雨燕

swift枚举与rust非常相似,通过支持"关联值",它们的行为类似于代数数据类型。

@adtclassTree:EMPTY:CaseLEAF:Case[int]NODE:Case["Tree","Tree"]
0

类型脚本

typescript通过一种称为"discriminated unions"的语言功能提供ADT

从他们的文档中可以看到这个示例:

@adtclassTree:EMPTY:CaseLEAF:Case[int]NODE:Case["Tree","Tree"]
1

安装

要在python项目中添加adt作为库,只需运行pip(或pip3,因为它可能在您的系统中命名):

@adtclassTree:EMPTY:CaseLEAF:Case[int]NODE:Case["Tree","Tree"]
2

这将安装来自pypi的最新版本。

mypy插件

该库还附带了一个用于mypy的插件,该插件允许对@adt定义进行类型检查。如果您已经在使用mypy,则需要该插件以避免无意义的类型错误。

要启用adt类型检查插件,请将以下内容添加到项目工作目录中的mypy.ini文件中:

@adtclassTree:EMPTY:CaseLEAF:Case[int]NODE:Case["Tree","Tree"]
3

定义ADT

要开始定义自己的数据类型,请导入@adtdecorator和case[…]注释:

@adtclassTree:EMPTY:CaseLEAF:Case[int]NODE:Case["Tree","Tree"]
4

然后,定义一个新的python类,在该类上应用@adtdecorator:

@adtclassTree:EMPTY:CaseLEAF:Case[int]NODE:Case["Tree","Tree"]
5

对于ADT将具有的每个案例(变量),使用case注释声明一个字段。用所有的大写名称声明字段是传统的做法,但唯一真正的限制是它们不能是小写的。

@adtclassTree:EMPTY:CaseLEAF:Case[int]NODE:Case["Tree","Tree"]
6

如果要将某些数据与特定案例关联,请在case之后的括号中列出该数据的类型(类似于generic[…]tuple[…]来自typing的注释)。例如,要添加带有关联字符串的案例:

@adtclassTree:EMPTY:CaseLEAF:Case[int]NODE:Case["Tree","Tree"]
7

只要列出所有类型,就可以用任意多个关联的数据片段构建案例:

@adtclassTree:EMPTY:CaseLEAF:Case[int]NODE:Case["Tree","Tree"]
8

ADT也可以是递归的,也就是说,ADT本身可以存储在一个特定的case旁边,尽管类名必须用双引号提供(这个限制也可以是适用于键入)。

递归adt的一个典型例子是链表。在这里,列表也被设置为泛型,覆盖类型t

@adtclassTree:EMPTY:CaseLEAF:Case[int]NODE:Case["Tree","Tree"]
9

有关完整ADT定义的更多示例,请参见库中的测试。

生成的功能

给定ADT定义如下:

@adtclassExpression:LITERAL:Case[float]UNARY_MINUS:Case["Expression"]ADD:Case["Expression","Expression"]MINUS:Case["Expression","Expression"]MULTIPLY:Case["Expression","Expression"]DIVIDE:Case["Expression","Expression"]
0

@adt装饰程序将自动生成以下形式的访问器方法:

@adtclassExpression:LITERAL:Case[float]UNARY_MINUS:Case["Expression"]ADD:Case["Expression","Expression"]MINUS:Case["Expression","Expression"]MULTIPLY:Case["Expression","Expression"]DIVIDE:Case["Expression","Expression"]
1

这些访问器可用于获取与ADT案例相关联的数据,但如果ADT不是用匹配的案例构造的,则访问器将引发异常。当您已经知道ADT对象的情况时,这是一个速记法。

@adt还将自动生成一个模式匹配方法,当您事先不知道您有哪种情况时,可以使用该方法:

@adtclassExpression:LITERAL:Case[float]UNARY_MINUS:Case["Expression"]ADD:Case["Expression","Expression"]MINUS:Case["Expression","Expression"]MULTIPLY:Case["Expression","Expression"]DIVIDE:Case["Expression","Expression"]
2

有关使用这些生成的方法的示例,请参见库的测试。

@adt还将生成方法(仅当它们不是已经定义的

自定义方法

可以在ADT上定义任意方法,只需将它们作为普通方法包含在类定义中即可。

例如,要在exampleadt上生成默认访问器的"安全"版本,它返回none而不是在情况不正确时引发异常:

@adtclassExpression:LITERAL:Case[float]UNARY_MINUS:Case["Expression"]ADD:Case["Expression","Expression"]MINUS:Case["Expression","Expression"]MULTIPLY:Case["Expression","Expression"]DIVIDE:Case["Expression","Expression"]
3

但是,不能将其他字段添加到类中,因为decorator将尝试将它们解释为adtcases(将失败)。

欢迎加入QQ群-->: 979659372 Python中文网_新手群

推荐PyPI第三方库


热门话题
java从资产中读取JSON文件   JavaFX上带线程程序的java暂停事件执行   java如何将datepicker格式转换为ddMMyyyy   多次点击swing Java按钮   java primefaces使用DefaultMenuItem同时进行重定向和操作   在JAVA中,如何将身份验证器POST请求中的会话转发到GET请求中?   用于在mongodb集合中插入包含元素数组的文档的json Java API   java DriverManager无法打开SQL连接器jar   java使用一个通用工厂方法创建实例   Java中的多线程等待条件   java Liferay 7.1限制了对虚拟实例(公司)的portlet/modules访问   安卓 Java:在读取进程的InputStream之前,等待进程的子进程完成   java只允许使用一种HTTP方法。找到,得到,放   docker我无法从我的java制作人生成kafka主题的消息