- 编程语言
- 编译器
- 语言运行时
- 开发工具
【免费下载链接】unison
A friendly programming language from the future
导读
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
相关推荐
Roc 编译器局部类型声明详解:块级作用域的类型别名、名义类型与不透明类型
Roc 编译器局部类型声明详解:块级作用域的类型别名、名义类型与不透明类型 本文基于 roc 语言仓库中的编译快照测试 test/snapshots/type_
PHP-Parser类型处理:类型声明解析支持
PHP Parser类型处理:类型声明解析支持 引言:现代PHP类型系统的演进 随着PHP语言的不断发展,类型系统经历了从弱类型到强类型的重大变革。从PHP 7
编译器静态分析代码生成zotero-style类型定义:global.d.ts全局类型声明深度解析
还在为Zotero插件开发中的类型错误烦恼吗?一文带你彻底掌握zotero style的全局类型系统,让你的开发效率提升200%! 全局类型架构全景 zoter
桌面应用知识管理科研