news 2026/10/10 5:46:05

Unison 语言中 Term 声明禁止携带哈希限定名:语法规则、解析器实现与转写测试验证

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Unison 语言中 Term 声明禁止携带哈希限定名:语法规则、解析器实现与转写测试验证
  • 编程语言
  • 编译器
  • 语言运行时
  • 开发工具

【免费下载链接】unison

A friendly programming language from the future

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

本文以 Unison 开源仓库中的转写(transcript)测试文档no-hash-in-term-declaration.output.md为线索,深入剖析 Unison 语言的一条核心语法约束:Term(项)声明的名称——无论是类型签名还是类型定义中的名称——均不允许包含哈希限定符(hash qualifier,如x##Nat)。读完本文,你将理解这一规则的作用范围、它在词法/语法解析层的落地方式、违反规则时产生的错误行为,以及如何通过仓库中的转写测试机制验证这条约束。

一、规则速览:Term 声明中不允许出现哈希

unison-src/transcripts/no-hash-in-term-declaration.md(其转写输出即本文关联文档 no-hash-in-term-declaration.output.md)用一句话定义了该规则:

There should not be hashes in the names used in term declarations, either in the type signature or the type definition. (Term 声明中所使用的名称不应包含哈希,无论是在类型签名中还是在类型定义中。)

也就是说,规则覆盖两种位置:

位置说明示例
类型签名(type signature)x : SomeType声明中的被声明名称x##Nat : Int -> Int -> Boolean
类型定义/函数体(type definition)函数定义x = ...左部(head)中的绑定名称x##Nat = 5

作为对照,哈希限定名(hash-qualified name)在 Unison 的其他语法位置上是被正常支持的:例如在表达式、类型引用中,可以通过Name#hash的形式精确定位某个名称对应的特定 hash(如引用base##Nat、.foo.bar#xyz)。被禁止的只是"在声明的位置上把名称写成带哈希的形式"。

二、转写测试:如何验证这条规则

仓库中的unison-src/transcripts/目录存放 Unison 的转写测试:每个.md文件包含一段"指令 + Unison 代码"的交互式脚本,由转写框架执行后生成对应的.output.md期望输出文件。

no-hash-in-term-declaration.md的完整内容为:

# No Hashes in Term Declarations There should not be hashes in the names used in term declarations, either in the type signature or the type definition. ``` unison :hide-all :error x##Nat : Int -> Int -> Boolean x##Nat = 5 ```

这里的关键是指令标记:hide-all :error:

  • :hide-all表示该代码块中的所有输出(包括错误信息)都不写入转写输出文件;
  • :error则表明该代码块预期以"出错"的方式结束——即这段代码应当解析失败。

因此对应的期望输出文件 no-hash-in-term-declaration.output.md 只保留了标题与规则说明两行,没有任何代码或错误文本:这正是"代码因含哈希声明名而失败、且错误被隐藏"的预期结果。如果未来某天 Unison 改变了该语法规则(例如允许哈希声明名),这段转写测试的.output.md将不再匹配实际输出,测试即可立刻暴露行为变化。

三、源码实现:解析器为何拒绝哈希

要理解x##Nat = 5与x##Nat : Int -> Int -> Boolean为何失败,需要沿 Unison 的"词法 → 语法"两层实现追查。

3.1 词法层:x##Nat可以被正常切分为哈希限定名

首先需要说明:x##Nat这样的写法在词法层面是合法的。在unison-syntax/src/Unison/Syntax/Lexer/Unison.hs中,identifierP解析一个标识符后,会通过P.optional shortHashP尝试读取可选的短哈希部分:

identifierP :: (Monad m) => P.ParsecT (Token Err) String m (HQ'.HashQualified Name) identifierP = do P.label "identifier (ex: abba1, snake_case, .foo.bar#xyz, .foo.++#xyz, or 🌻)" do name <- PI.withParsecT (fmap nameSegmentParseErrToErr) Name.nameP P.optional shortHashP <&> \case Nothing -> HQ'.fromName name Just shorthash -> HQ'.HashQualified name shorthash

即:若标识符后面跟了#...短哈希,就构造HQ'.HashQualified name shorthash;否则是纯名称HQ'.fromName name。所以x##Nat会被词法器产出一个携带 hash 的HashQualified Name词元(lexeme),对应文档注释中的合法示例.foo.bar#xyz、.foo.++#xyz。

3.2 语法层:prefixTermName明确拒绝带哈希的名称

问题出在语法解析层。在unison-syntax/src/Unison/Syntax/Parser.hs中,定义了两个行为不同的前缀标识符解析器:

-- | Parse a prefix identifier e.g. Foo or (+), discarding any hash prefixDefinitionName :: (Var v) => P v m (L.Token v) prefixDefinitionName = wordyDefinitionName <|> parenthesize symbolyDefinitionName -- | Parse a prefix identifier e.g. Foo or (+), rejecting any hash -- This is useful for term declarations, where type signatures and term names should not have hashes. prefixTermName :: (Var v) => P v m (L.Token v) prefixTermName = wordyTermName <|> parenthesize symbolyTermName where wordyTermName = queryToken \case L.WordyId (HQ'.NameOnly n) -> Just $ Name.toVar n _ -> Nothing symbolyTermName = queryToken \case L.SymbolyId (HQ'.NameOnly n) -> Just $ Name.toVar n _ -> Nothing

源码注释直接点明了设计意图:"This is useful for term declarations, where type signatures and term names should not have hashes"(该解析器用于 term 声明,其中类型签名与 term 名称不应含有哈希)。其实现核心是模式匹配:

  • 只有词元是HQ'.NameOnly n(即未携带哈希的纯名称)时才接受;
  • 一旦词元是HQ'.HashQualified ...(携带哈希),两个分支都会落入_ -> Nothing,解析即失败。

与之形成对照的是prefixDefinitionName/wordyDefinitionName,后者通过Name.toVar (HQ'.toName n)丢弃哈希而只取名称,因此foo#abc在类型/构造器定义等位置是允许出现的:

-- | Parse a wordy identifier e.g. Foo, discarding any hash wordyDefinitionName :: (Var v) => P v m (L.Token v) wordyDefinitionName = queryToken \case L.WordyId n -> Just $ Name.toVar (HQ'.toName n) _ -> Nothing

3.3 两个声明位置的实际调用点

prefixTermName在parser-typechecker/src/Unison/Syntax/TermParser.hs中被用于 term 声明的两类关键位置:

(1)类型签名(typedecl)——x : T形式的声明头部,对应文档中x##Nat : Int -> Int -> Boolean的失败场景:

typedecl :: (Monad m, Var v) => P v m (L.Token v, Type v Ann) typedecl = (,) <$> P.try (prefixTermName <* reserved ":") <*> TypeParser.valueType <* semi

prefixTermName <* reserved ":"意味着:签名左侧的名称必须是纯名称,其后紧跟冒号。x##Nat因携带哈希而无法通过prefixTermName,该声明解析失败。

(2)函数定义左部(definition head)——对应文档中x##Nat = 5的失败场景。在 term 定义解析时,左部(lhs)由中缀形式与前缀形式组成:

let infixLhs = do (arg1, op) <- P.try $ (,) <$> prefixDefinitionName <*> symbolyDefinitionName arg2 <- prefixDefinitionName pure (ann arg1, op, [arg1, arg2]) let prefixLhs = do v <- prefixTermName vs <- many prefixTermName pure (ann v, v, vs) let lhs :: P v m (Ann, L.Token v, [L.Token v]) lhs = infixLhs <|> prefixLhs

注意prefixLhs中绑定名(v)与全部函数参数(vs)都使用prefixTermName解析;infixLhs中用于中缀操作符两侧操作数的prefixDefinitionName虽然会丢弃哈希,但中缀形式要求第一个 token 为符号型标识符(symbolyDefinitionName),与x##Nat的词形也不匹配。因此x##Nat = 5中定义头x##Nat无法作为合法的绑定名被接受。

四、设计动机:为什么声明名要禁止哈希

从源码注释与周边设计可以推断,这条约束服务于两个目的:

  1. 声明的确定性:一个 term 声明是在"引入一个新名称",而哈希限定名表示"指向某个已存在、特定 hash 的实体"。允许在声明位置写x##Nat会造成语义混淆——究竟是定义新名称x,还是断言x对应某个既有 hash?
  2. 与声明名称的其他约束一致:声明名还有额外的合法性要求。例如parser-typechecker/src/Unison/Syntax/TermParser.hs中的verifyRelativeName'会拒绝以.开头的绝对名称(DisallowedAbsoluteName);声明名称必须是相对名称且无哈希,保证绑定名始终是"干净、可被命名空间解析的名称"。

五、给使用者的实践要点

  • 在.u源文件中声明 term 时,类型签名x : T与函数定义x = ...中的x必须是纯名称(可含命名空间段,如foo.bar),不要加#hash后缀。
  • 若确实需要引用某个特定 hash 的既有定义(例如跨库重名消歧),请把哈希限定名用在引用位置(表达式中),而不是声明位置。
  • 符号型标识符(如(+))同样受此约束:(+)##a : ...这类写法会被symbolyTermName的HQ'.NameOnly匹配拒绝。
  • 想要回归验证该行为,可直接运行仓库的转写测试:no-hash-in-term-declaration.md使用:hide-all :error断言"带哈希的 term 声明必然解析失败",其期望输出保存在 no-hash-in-term-declaration.output.md,与unison-src/transcripts/目录下的其他转写用例共同构成对 Unison 语法规则的自动化守护。

六、小结

"Term 声明名称禁止携带哈希"是 Unison 语法设计中的一条小而关键的铁律:它由词法器(identifierP支持产出哈希限定词元)与语法器(prefixTermName只接受NameOnly)的配合实现,覆盖类型签名(typedecl)与函数定义头(prefixLhs/infixLhs)两类位置,并有对应的转写测试持续验证。对使用者而言,记住一句话即可:声明要干净,引用可带哈希。

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

【免费下载链接】unison

A friendly programming language from the future

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

相关推荐

上一篇:终极Citra模拟器使用指南:如何完美运行3DS游戏的完整解决方案
下一篇:黑苹果配置革命:OpCore-Simplify让OpenCore配置从8小时缩短到30分钟

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/10 5:40:35

App Store审核4.3a被拒自救:uniapp跨端应用改IPA加垃圾数据原理与实操

这一天还是来了。从事或接触过 iOS 开发的朋友多半能会心一笑&#xff1a;App Store 审核里的 4.3a&#xff0c;也就是术语里的“垃圾应用”或“重复应用”判定&#xff0c;大概是这些年让不少团队最头大、最说不出道理的一条规则。尤其是用 uniapp 这类跨端框架打包的 App&…

作者头像 李华
网站建设 2026/10/10 5:40:08

Python项目CI/CD实战:从依赖管理到自动化部署的完整指南

1. 为什么Python项目也离不开CI/CD先说个我踩过的大坑。几年前维护一个内部Python工具库&#xff0c;二十来号人往里提交代码&#xff0c;每次合并都是手动在本地跑一遍测试再推上去&#xff0c;结果几乎每个月都会出现"我这边明明能跑啊"的灵异事件。后来实在受不了…

作者头像 李华
网站建设 2026/10/10 5:38:11

从爱迪生《点亮黑夜》看创新方法论与试错法

《点亮黑夜》这本书&#xff0c;我读了两遍才敢说读懂了一半。它讲的是爱迪生&#xff0c;但它不是那种“发明大王”的儿童励志读物——作者艾德蒙莫里斯&#xff08;Edmund Morris&#xff09;用六百多页的篇幅&#xff0c;把这个被符号化了的名字重新还原成一个复杂、执拗、有…

作者头像 李华
网站建设 2026/10/10 5:38:07

技术思维升级指南:从第一性原理到认知框架的底层革命

1. 一场关于“怎么想”的战争技术圈最近有个很有意思的现象&#xff1a;大家不聊框架、不聊模型参数了&#xff0c;开始聊“思维”。从“第一性原理”到“算法思维”&#xff0c;从“认知升级”到“心智模型”&#xff0c;朋友圈里铺天盖地全是这类词。但说实话&#xff0c;真正…

作者头像 李华
网站建设 2026/10/10 5:37:36

大规模电动汽车随机充放电优化:局部求解策略与MATLAB实现

最近帮一个园区做充电桩配套调度方案时&#xff0c;最头疼的问题就是晚间六点到九点&#xff0c;一两百辆车同时扎进电网。车主到达时间不确定、剩余电量不确定、第二天出发时间也不确定——这其实就是一个典型的大规模电动汽车随机充放电优化问题。起初我的想法比较天真&#…

作者头像 李华
网站建设 2026/10/10 5:35:34

【单片机毕设案例分享】基于单片机的室内多传感环境感知与超标自动通风预警系统设计 基于单片机的图书馆光照与室内空气质量远程监测报警装置设计(030112)

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于单片机&#xff0c;STM32单片机&#xff0c;51单片机&#xff0c;J…

作者头像 李华