Typst: Designing for Incrementality
摘要
Typst 通过约束记忆化(comemo)和纯函数设计,使语言和编译器协同工作,实现高效的增量编译和实时预览。文章详细介绍了布局缓存、模块评估记忆化、函数纯度以及内省系统的设计思路。
<p><a href="https://lobste.rs/s/hj0exw/typst_designing_for_incrementality">Comments</a></p>
查看缓存全文
缓存时间: 2026/06/29 16:31
TL;DR: Typst 通过约束记忆化(comemo)和纯函数设计,使语言和编译器协同工作,实现高效的增量编译和实时预览。
## 引言:Typst 的设计目标
Typst 是一个基于标记的排版系统,包含自己的标记语言、样式系统(`set` 和 `show` 规则)、完整的脚本层以及内省系统。设计上追求易学、使用愉悦,核心目标是实现**实时预览**——这在 2026 年的硬件条件下完全可行。关键在于**将语言和其实现共同设计**,以支持高性能增量编译。
## 增量编译的核心:记忆化
为了在编辑文档时复用已编译的结果,Typst 使用**记忆化**(memoization)来记住编译中间结果。
### 布局的简单记忆化
布局函数接收段落、宽度等输入,返回一个固定尺寸的框架(frame)。它没有副作用,是纯函数。因此只需要对输入进行哈希(使用 `xxhash`),将结果存储在哈希映射中即可。这是最基础的缓存策略,效果显著,减少了布局开销。
### 模块评估与约束记忆化
脚本系统包含模块,使用 `include` 和 `import` 组织文件。如果直接记忆化 `eval` 函数,输入参数包括源文件和文件系统。但文件系统会随着外部文件变化而变化,导致所有 `eval` 调用失效。
Typst 使用 **comemo**(约束记忆化)crate 解决:将文件系统包装在 `Tracked` 类型中。每次 `eval` 调用 `fs.load` 时,记录下实际读取的文件路径和其哈希值,形成一组“约束”。评估完成后,将约束与输出一起存入缓存。下次调用时,先哈希非跟踪参数找到对应条目,再逐一检查约束是否匹配。只有实际读取的文件发生变化时,缓存才失效,避免了全局重新编译。
## 函数与纯度
Typst 的用户函数也必须可缓存。思路是强制函数为**纯函数**——没有可变引用、没有全局状态。这会不会让脚本变成 Haskell?实际并不。Typst 更像“没有引用的确定性 Rust”,允许局部可变性(如 for 循环),但可变性被封装在函数内部。通过值语义和引用计数(`Arc`)实现高效数据传递,无需生命周期。例如返回向量时,克隆到缓存只需 `arc_bump`(增加引用计数),性能优异。局部可变性通过 `Arc::make_mut` 实现:检查引用唯一性,唯一时直接修改,否则克隆再修改。
这种设计保证了缓存正确性,同时保持脚本的易用性。
## 排版系统的挑战:计数与目录
纯函数无法直接使用可变计数器(如标题编号)或查询文档内部状态(如目录)。计数器看似简单,但目录和交叉引用实际上是同一问题的两面:都需要文档自省。
### LaTeX 的多轮迭代
LaTeX 通过将信息写入临时文件(如 `.toc`)并多次运行编译器来解决。用户需要反复运行,用 `latexmk` 自动迭代直到稳定。这是“不断重试直到收敛”的方法。
### Typst 的内省系统
Typst 不依赖可变性,而是提供**内省**(introspection)能力:文档可以观察自身结构。例如,可以询问“文档中的第一个标题在哪里?”,并获取其页面坐标。这样符号编号(如章节编号)和目录都可以通过自省实现,而非依赖可变计数器。这依然保持了纯函数的约束,同时用更声明式的方式解决了传统排版系统的痛点。
## 总结
Typst 通过将编译器的记忆化特性与纯函数语言设计紧密结合,实现了高效的增量编译。布局缓存、约束记忆化模块评估、强制函数纯度以及内省系统,共同构成了 Typst 实时预览的基础。这种“共同设计”思路使得语言语法和实现互相助力,而非成为彼此的负担。
Source: https://youtu.be/yWWVhbyOWWE
相似文章
@seclink: sccache 与 incremental 互斥的经典坑: 两者同时开,结果是既占了 20GB 增量缓存,依赖又每次重编。关掉 incremental 后: 大型第三方库(tokio/serde/axum/reqwest 等)编译一次,跨…
解释了sccache与incremental编译互斥的问题:同时开启会导致增量缓存占用20GB且依赖每次重编译。关闭incremental后,大型第三方库编译一次即可跨项目复用,首次编译后缓存命中率达53–93%。
测试时记忆构建
提出 T2Mem 框架,将记忆构建延迟到推理阶段,随推理过程渐进式构建面向查询的记忆,在 LoCoMo 和 LongMemEval 上超越强基线并显著降低记忆构建开销。
Valen 的内存安全:一种新型借用检查
Valen 的新文章介绍了一种更灵活的借用检查器(group borrowing),编译器会记住引用指向的位置,以实现比 Rust 更简单灵活的内存安全方案,并已被 Google Carbon 等语言采纳。
@billtheinvestor: 给 Claude Code 和 Codex 无限记忆,编程效率提升92%!Agentmemory 工具已在 GitHub 上火速获得4000+个星标,完全免费。 它通过智能压缩保存你编程会话的所有信息,未来会话中自动提取相关上下文,避免重…
Agentmemory 是一个开源工具,为 Claude Code 和 Codex 提供无限记忆功能,通过智能压缩减少 token 使用量,提升编程效率,已在 GitHub 获得 4000+ 星标。
利用智能体记忆理解并缓解推理时过度依赖
该论文研究了LLM智能体中的'记忆过度依赖'现象——即使记忆是良性的、正确存储且被恰当检索,部分证据迁移仍会误导推理。作者提出MemTrim框架,在写入时索引记忆证据、在读取时控制复用,无需重训即可减少过度依赖。