欢迎光临散文网 会员登陆 & 注册

在 Typescript 中 实现 ADT(Algebra Data Types)

2022-08-23 18:03 作者:友纪V-入OP  | 我要投稿

Algebra Data Types,代数数据类型,是一个函数式编程中非常有趣的概念,从 java 角度来看,ADT 可以认为是一种模式,它看着就像带上数据的枚举类型,似乎平平无奇,但使用代数数据类型能让我们更好地进行类型建模,表达更多的东西并避免运行时异常(典型如空指针)。

问题在哪里?

考虑这样的一个(简化的)场景,我们要去实现一个 TODO APP,其中每条 TODO 称为 Task,Task 有如下性质:

  1. Task 有四种状态:已完成,未完成

  2. Task 有两种种类:某日的 Task,某日期区间的 Task(即该 Task 在某日期区间内都持续进行,比如进行三天的旅行,摸鱼一个星期……)

于是,设计出数据库表后,我们编写了这样的实体(Java 语言描述):

Java 开发时一般来说就是这么干的(枚举可能换成数据字典项啥的以节约空间,方便序列化),但这里有一个严重问题:

一些字段和其它字段是相互关联的,并且这种关联性并未在类型定义上体现出来,比如对于 startDate 和 endDate,其显然在 type 为 DURATION 的时候才有意义即非 null;对于 taskDate,其只在 type 为 DAY 时有意义;对于 doneDate,其只在 status 为 DONE 时有意义……这种关联性必须通过文档,注释等手段去表达

而且,要获取这些相关联的数据时,必须要先对 type 和 status 进行判断才能保证正确,但若程序员因不熟悉业务等情况忘记进行判断会如何?bug 可能就要出现了,而编译器并管不了这个。

Java 中的 ADT

解决方案呢?我们可以让 Task 成为一个富血对象,严格限制访问域,在 getter,setter 中去实现约束,比如下面实现一个 startDate 的 getter:

这能强迫使用者去关心相关的约束,但仍旧显得很繁琐,遗憾的是在 Java 添加 sealed 关键字和模式匹配之前,将 ADT 应用在业务代码上确实没有多少合适的解决方案。

一个可能的解决方案是什么呢?我们可以把和 Task 的状态相关的字段和这个状态绑在一起,比如对于 TaskStatus 这个状态,我们可以为每个情况定义相应 Class:

“带上数据的枚举类型”!在实际操作时,对 status 就必须使用 instanceof 去判断它的类型了,判断后进行类型强转即可获取其值。

但这里仍旧有一个问题:强转是向下转型,仍旧需要使用者去确定强转的类型,且 IDE 不一定补全正确的类型,因此有一定心智负担,且仍旧可能出错。

一个简单的优化方案是在 TaskStatus 中添加相应方法去获取数据:

在 Kotlin,Scala,Typescript 语言中去实现的 ADT 实际上在很大程度上和这种方法(模式)是一致的,但是搭配上这些语言的模式匹配功能(以及 sealed, case 等关键字),用起来就会顺滑无比,从而真正在工程实践中有应用价值。

Typescript 中的 ADT

在 Typescript 中去实现 ADT 轻而易举,因为它本身就包含所谓的或(积 Product)类型 (|运算符),比如上面的 TaskStatus 可以直接这么表达:

Typescript 足够聪明,用户只需要对 _tag 进行判断,它就能够知晓该数据的类型究竟是 TaskDone 还是 TaskNotDone,因此我们可以用 switch 去做简单的模式匹配(并且输入 case 的时候也能得到补全!)。

React 的 Reducer 的 Action 类型也可以看作 ADT,这时它的 type 属性就代表它的类型:

但在这里有一个问题——这样操作的话没法往 ADT 上面添加方法了,这对面向对象语言还是非常难受的,但仍旧可以解决,如下面的代码实现 Typescript 版的 Maybe:

这就舒服了。

tips: Haskell 的 ADT 类型定义类似data Maybe a = Nothing | Just a,其中 Maybe 称为类型构造器,Nothing 和 Just 对应值构造器,Maybe 对应这里的 Optional 类型,Nothing 和 Just 对应两个同名函数

参考资料

《Algebraic Data Types in TypeScript》,https://www.susanpotter.net/software/algebraic-data-types-in-typescript/


在 Typescript 中 实现 ADT(Algebra Data Types)的评论 (共 条)

分享到微博请遵守国家法律