☰
深入解析 Unison 的类型声明:结构类型、唯一类型与不透明类型的演进之路
2026/10/8 19:18:24 网站建设 项目流程
  • 编程语言
  • 编译器
  • 语言运行时
  • 开发工具

【免费下载链接】unison

A friendly programming language from the future

项目地址:https://gitcode.com/gh_mirrors/un/unison
点击查看免费下载

导读

docs/type-declarations.markdown是 Unison 语言设计早期(标记为 draft)的一份核心设计文档,它系统性地讨论了三种数据类型声明形态——结构类型(Structural Types)、唯一类型(Unique Types)与不透明类型(Opaque Types)——各自的身份(identity)定义方式、构造子唯一性约束、模块化封装能力,以及三者之间的组合可能性。本文以该文档为主线,结合当前仓库中的 DataDeclaration 实现、V2 哈希实现 与词法器源码,还原这些设计决策的来龙去脉,并对照>data DataDeclaration' v a = DataDeclaration { annotation :: a, bound :: [v], constructors' :: [(a, v, AnnotatedType v a)] -- isStructural :: IsStructural -- isOpaque :: Set (AnnotatedTerm v a) } deriving (Eq, Show, Functor) -- type IsStructural = Structural | Unique GUID

这段草稿代码与当前仓库中的最终实现高度对应。在 unison-core/src/Unison/DataDeclaration.hs#L115-L121 中,DataDeclaration已被整理为:

data DataDeclaration v a = DataDeclaration { modifier :: Modifier, annotation :: a, bound :: [v], constructors' :: [(a, v, Type v a)] } deriving (Eq, Ord, Show, Functor, Generic)

草稿中被注释掉的isStructural :: IsStructural与isOpaque :: Set (AnnotatedTerm v a),正是对"类型身份由什么决定"这一核心问题的探索痕迹;最终它们收敛为一个独立的Modifier代数数据类型:

data Modifier = Structural | Unique Text -- | Opaque (Set Reference) deriving (Eq, Ord, Show)

在 V2 哈希模块 中,Modifier的定义与之一致,而注释中的Opaque (Set Reference)也原样保留——这说明不透明类型在当时仍是"悬而未决"的候选方案,尚未进入最终的类型系统。从源码结构可以推断,Opaque 在正式实现中并未落地,Unique 则通过 GUID(Text)实现,Structural 成为默认形态。

类型身份为什么如此重要

文档虽然没有长篇展开背景,但配套文档>structural type Maybe a = Nothing | Just a structural type Optional t = Some t | None

由于结构类型的身份完全由构造子的结构(签名)决定,Maybe与Optional是同一个类型。更进一步:

structural type Validation e a = Success a | Failure e structural type Either a b = Left a | Right b

即使语义上并不应该等同(Validation与Either的语义差异明显),它们仍然会因结构相同而互通——文档指出这"也许本不该如此",属于设计上需要接受的现实。配套文档>unique type Day = Mon | Tue | Wed | ... unique[<guid>] type Day = Mon | Tue | Wed | ...

从解析器源码可以印证 GUID 的两种来源路径:Parser.hs#L200-L204 中,解析环境带有一个uniqueTypeGuid :: Name -> m (Maybe Text)回调——每当遇到未显式指定 GUID 的unique type声明时,先按名字查询是否已有可复用的 GUID(Just则复用),否则调用uniqueName 32生成一个 32 字符的 base32hex 随机标识。这与文档中"GUID 通常自动生成、但可显式指定以复用相同类型"的设计完全吻合。

构造子的有序性问题

文档给出了一个在当时仍未敲定的细节:

Order of constructors having the same type is stable, but the relative constructor order of differently typed constructors is (currently) unspecified.

即:同类型构造子之间的相对顺序是稳定的,而不同类型构造子的相对顺序(当时)未定义。对比>opaque type Socket = Socket Nat opaque type Handle = Handle Text

文档随即抛出两个尚待解答的问题:

  • 如何声明一个能检查两种不透明类型的定义?
  • 我们自己(语言实现者)如何创建和检查 Socket?不想公开访问器,但又需要某种方式让"特权代码"构造这些值——单构造子类型较简单,多构造子情形可能需要一种确定性的区分方式。

与 Scala 不透明类型的对比

文档引用了 Scala SIP(opaque types)作为参照,并记录了三点观察:

  • Scala 的不透明类型本质上是类型别名(无装箱),只在对应的伴生对象/模块内部相等;
  • Unison 则需要把值"装箱"进构造子,以获得与类型身份对应的哈希;
  • 因此两者在实现机制上存在本质差异。

另一种思路:friend 注解与私有函数

文档随后记录了一条来自社区讨论的替代方案(线索指向 2019 年的 Slack 讨论,文内附有链接):与其强制改变类型身份才能添加特权函数(这有违"开放世界"精神),不如让不透明类型的身份完全等同唯一类型,再额外允许将某些项标注为某类型的"friend",从而获准创建/模式匹配其值。草案给出如下语法:

friend[Foo, Bar] eg : Foo Bar eg = Foo.Foo 1 "hi" (Bar.Bar 3.1) -- syntax reminiscent of unique[#af361]

这种注解是挂在项上的元数据,语言可以提供"列出某个类型的所有 friend"的能力,以评估"特权代码"的足迹(footprint)。

在此基础上,文档进一步提出private[Foo]注解:它既蕴含friend[Foo],又限定只能被其他friend[Foo]项引用。理由是仅靠控制"创建 + 模式匹配"并不足以覆盖前述三个模块化目标——库内部的辅助函数仍可能被以破坏不变量的方式调用,或将来被改动/移除,或与 API 不在同一语义层级。

实现现状:Opaque 未落地

需要如实指出:从当前仓库源码看,不透明类型并未作为独立语言特性实现。Modifier中Opaque (Set Reference)仅存在于注释里(见 unison-core/src/Unison/DataDeclaration.hs#L105 与 unison-hashing-v2/src/Unison/Hashing/V2/DataDeclaration.hs#L29)。仓库中"opaque"字样多数指向运行时内部的Canonicalizer值(如 unison-runtime/src/Unison/Runtime/Canonicalizer.hs#L114),与语言层面"不透明数据类型"是两回事。内置类型(如MVar)的"伪不透明"是通过Reference.Builtin机制实现的,详见 adding-builtins.markdown 中对builtinTypesSrc与B' "MVar" CT.Data的说明——那是另一条实现路径(内置引用 + 名称合并),而非用户级的不透明类型语法。

组合规则:哪些形态可以叠加

文档用一张简明的表回答了组合问题:

组合结论说明
Structural + Unique否身份机制互斥
Structural + Opaque否身份机制互斥
Unique + Opaque可以Opaque 隐含 Unique

由此得到关键推论:Opaque 蕴含 Unique。文档给出的判别用例极具指导性:

  • 需要Opaque 但不需 Unique的场景:SortedSet——其语义完全由暴露的方法定义,两个实现相同方法的集合类型可以视为同一类型;
  • 需要Unique + Opaque的场景:Socket、Handle——暴露的方法决定了这两个类型必须保持不同身份,即使内部表示相同。

这一组合矩阵与Modifier = Structural | Unique Text的最终落地形态一致:一旦 Opaque 落地,它必然建立在 Unique 之上。

遗留的设计讨论:构造子顺序与代数类型

构造子显示顺序:应作为可编辑元数据

文档记录了一个真实场景:一位开发者为了可读性把IsOptional类型的构造子从Optional / Required / ZeroPlus / OnePlus调整为Optional / Required / ZeroPlus / OnePlus(Required 提前),但语义完全没变,仍希望这是同一个类型——而当时提出的所有类型实现都无法做到这一点。作者(Paul Chiusano,@pchiusano)的回应是:"构造子显示顺序"应作为附着在类型声明上的元数据,允许被编辑;add/update命令在默认情况下甚至可以针对这类修改"建议元数据编辑"。

这个讨论最终如何收场,可以参考>

  • 编程语言
  • 编译器
  • 语言运行时
  • 开发工具

【免费下载链接】unison

A friendly programming language from the future

项目地址:https://gitcode.com/gh_mirrors/un/unison
点击查看免费下载

相关推荐

上一篇:BiSheng ReBAC 列表性能优化实战:Cursor 分页 + 无限滚动改造全解析(F027)
下一篇:如何快速上手Laravel-api-generator?5分钟搭建RESTful API的实战教程

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询