TypeScript 一行搞定的联合类型,Haskell 要绕多远
来源:garciat.com — 2026-10
📋 概述
作者喜欢 TypeScript 的联合类型(匿名和类型)与字面量类型(单例类型)的组合:配合判别字段,可以既精确又简短地表达"成功时带角色与头像尺寸、失败时带错误信息"这种 API。换成不开扩展的 Haskell,就得为 UserRole 与 AvatarSize 各写一个 ADT,真实取值 96、128、512 被 AvatarSize96 这类标签遮住。于是他试了两条扩展路线:DataKinds + GADTs 的 OneOfNat,用嵌套的 Here/There 承载类型级自然数,能跑但语法是嵌套的;DataKinds + UnboxedSums 的 (# SNat 96 | SNat 128 | SNat 512 #),语法扁平好看,但每种元数的 unpacked sum 是不同的 kind,导致无法用类型族对元数做抽象(GHC 会报 kind 不匹配)。他还点出更根本的差距:两条路都不具备"联合类型像集合那样去重"的语义,改用类型级 Set 只会让表示更复杂。
🔑 核心要点
- 对比的动机是 API 精度:TypeScript 用联合加字面量类型,一行就写出带判别字段的精确形状
- Haskell 的裸方案是有代价的:必须为每个取值范围声明 ADT,真实取值 96/128/512 被 AvatarSize96 这类标签遮住
- 路线一是嵌套表示:DataKinds + GADTs 的 OneOfNat 用 Here/There 编码类型级列表,可用但语法层层嵌套
- 路线二更漂亮但撞 kind:UnboxedSums 语法扁平,然而不同元数的 unpacked sum 属于不同 kind,类型族无法抽象
- 语义缺口才是关键:两条路都不做集合式去重,重复类型不会被归一,改类型级 Set 只会更复杂
💡 金句
而且,这两种替代方案都没有达到我们想要的语义——让类型联合像一组类型那样行为,也就是把重复项归一。
👍 0
👎 0
← 返回 Lobsters 首页