为什么选择Janet?(2023)
摘要
一篇倡导Janet编程语言的文章,突出其简洁性、易于分发、通过PEG实现的强大解析能力以及优秀的子进程DSL,使其成为副项目和命令行工具的理想选择。
暂无内容
查看缓存全文
缓存时间: 2026/06/02 15:32
# 为什么选择 Janet?
来源:https://ianthehenry.com/posts/why-janet/
我从没想过这种事会发生在我身上。我是说,括号?在这个年代?但在过去的几年里,我用来做有趣小项目的首选编程语言,一直是一个叫 **Janet**(https://janet-lang.org/) 的小型 Lisp 方言。
```
(print "hey janet")
```
我非常喜欢 Janet,所以**我写了一整本关于它的书**(https://janet.guide/),并免费发布在互联网上,希望能吸引更多人成为 Janet 用户。
我觉得你应该读读它,但我知道你不相信我,所以我会试着说服你。下面是我的推销词:这就是为什么你 —— *就是你* —— 应该给 Janet 一个机会。
## Janet 很简单
Janet 是一种命令式语言,拥有一等函数、单一标识符命名空间和词法块级作用域。语言核心非常小,只有八条指令:`do`、`def`、`var`、`set`、`if`、`while`、`break`、`fn`。但得益于宏,有许多高级包装器提供更强大或更方便的控制流。
你可以在一个下午“学会” Janet,因为运行时语义非常熟悉:想想 JavaScript,再加上值类型,减去所有那些古怪的行为。其余的语言也很小:整个标准库**一页纸就能装下**(https://janet-lang.org/api/index.html)。正是这种易于上手的特点,最初让我爱上了它。
## Janet 是可分发的
很容易将 Janet 程序编译成原生可执行文件,并静态链接 Janet 运行时。你可以把那些程序分享给别人,而不需要他们先安装 Janet —— 或者你的项目依赖,或者任何其他东西。你甚至不必告诉他们这是用 Janet 写的!
Janet 实现这一点的方式非常优雅:Janet 将自身编译成字节码,然后把这些字节码写到一个 `.c` 文件中,该文件也会启动 Janet 运行时。然后它用你系统的 C 编译器编译那个 C 文件。由于 Janet 被设计成易于嵌入,这完全合情合理:本质上,它把自己嵌入到了一个简单的 C 可执行文件中。
一个简单的 Janet “hello world” 编译成原生二进制文件后不到一兆字节(在 macOS aarch64 上,Janet 1.27.0 为 784K,但你的情况可能有所不同)。这包含了完整的 Janet 运行时、垃圾回收器,甚至还有字节码编译器 —— 所以你可以编写在运行时评估 Janet 代码的程序,**如果你愿意的话**(https://bauble.studio/)。
这使得 Janet 成为编写小型命令行应用的绝佳选择。尤其当你考虑到…
## Janet 在文本解析方面强得不真实
Janet 的文本处理不是基于正则表达式,而是基于**解析表达式语法**。解析表达式语法比正则表达式更简单、更强大、更可预测。它们不是面向行的,所以可以轻松解析多行文本。它们也可以解析 HTML、JSON 或其他任何非正则语言。它们甚至能解析**二进制**文件格式 —— 对于任意空字节毫无问题。
它们确实是**解析器**:结构化的、可组合的、一等公民的解析器。**而且它们很容易学!**(https://janet.guide/pegular-expressions/)
## Janet 拥有任何高级语言中最好的子进程 DSL
有一个名为 `sh`(https://github.com/andrewchambers/janet-sh) 的第三方库,它提供了一个 shell 脚本 DSL,让你可以直接在 Janet 中表达管道和重定向。就像这样:
```
($ find . -name *.janet | say)
```
这非常不可思议。它的 DSL 是如此出色,以至于**我在《面向凡人的 Janet》中专门用了一整章**(https://janet.guide/scripting/)来介绍它 —— 以及你能用它做些什么。它让 Janet 从 Perl 的一个合理替代品提升到了一个令人惊讶的大范围程序中 **Bash 的合理替代品**。
## Janet 是可嵌入的
Lua 已经成为事实上的“嵌入式语言”,这很遗憾,因为……嗯,这不是一篇关于 Lua 的文章。你可能不太关心这个,但这可能只是因为你还没试过:能够编写**暴露脚本接口的程序**(https://bauble.studio/)是一种非常有趣的超能力。
嵌入 Janet 非常容易:Janet 运行时是一个小型 C 库,你只需要链接它,然后调用普通的 C 函数来操作 Janet 值。你甚至可以把 Janet 嵌入到**网站**中,并用**自定义可编程 DSL 构建静态站点**(https://toodle.studio/)!
## Janet 有可变和不可变集合
Janet 的集合类型有可变和不可变两种。不可变集合具有值语义:不可变向量 `[1 2]` 与 `(take 2 [1 2 3])` 是无法区分的,尽管它们有不同的内存地址。而可变集合具有引用语义:哈希表 `@{:x 1 :y 2}` 只等于它自身。另一个具有相同键和值的哈希表是独立的对象。
不是每种语言的标准库都内置了不可变的复合值!
## 宏,宏,宏
我认为这是你真正应该学习 Janet 的原因,但我不想一开始就说这个,因为我不想吓到你。
不学如何编写宏,你也能很好地使用 Janet。但你应该学,因为编写宏**很有趣**。它感觉与我之前做过的任何编程都不同。
编写宏需要同时思考两层:你正在编写代码来生成代码,所以你必须理清两条执行线索:一条是现在(编译时)运行的代码,它操作值和抽象语法树;另一条是你正在操作的代码,你生成的应用代码,将在未来运行的代码。
Janet 的宏不是卫生的,并且 Janet 没有独立的函数命名空间。但通过允许你反引用字面函数,Janet 使得编写完全引用透明的宏成为可能。这是一个非常简单而优雅的解决方案,解决了**一个本来非常微妙的问题**(https://ianthehenry.com/posts/janet-game/the-problem-with-macros/)。而 Janet 能做到这一点,也凸显了我的下一个最喜欢的特性……
## Janet 让你可以传递编译时的值到运行时
在我看来,这是 Janet 最有趣的地方。但一开始可能听起来并不那么有趣 —— 其实它只意味着任何 Janet 值都可以序列化到磁盘,并在以后读取回来。
但这种序列化是隐式的:当你编译一个 Janet 程序时,它会执行所有顶层指令 —— 普通语句、函数声明等等 —— 然后,一旦它执行完所有顶层值,Janet 会将程序状态的快照写入磁盘。
这是一个**完整**的程序状态快照:共享引用被保留,所以可变值在“恢复”快照后仍然可以被修改。生成器能准确记住下次恢复时需要执行的指令。闭包该怎么闭合就怎么闭合。
宏是编译时代码执行的一个特例 —— 操作抽象语法树来创建新函数 —— 但即使没有宏,你也可以享受这种超能力。做游戏?提前分析你的样条曲线!或者通过在编译时读取文件,将资源嵌入最终二进制文件——你可以执行任意的副作用!
*《面向凡人的 Janet》* 中有一个例子,演示了如何利用这一点根据 SQL schema 文件自动生成数据库绑定(https://janet.guide/macros-and-metaprogramming/)—— 这个例子有点傻,但却是大多数语言中很难做到的事情。
## Janet 用起来手感很好
这完全是主观感受,但我喜欢 Janet 的语法。它在简单性、统一性和多样性之间达到了完美的平衡。
它大量使用括号,但用 `[]` 表示列表,用 `{}` 表示表,来打破单调。
可变字面量总是以 `@` 开头:`@"可变字符串"`、`@{:不可变哈希表}` 等。
匿名函数写作 `(fn [x] (+ 1 x))`,但有一种简写符号可以用 `|` 将任何表达式提升为函数:`|(+ 1 $)`。
Janet 支持用 `;` 表示“展开”或“散列”:`(+ ;args)`。
字符串字面量可以用任意数量的反引号编写,并用相同数量的反引号闭合。像 `\n` 这样的转义序列在反引号引用的字符串中不适用,所以你可以创建包含任何内容的字符串,而完全不必考虑如何转义 —— 你只需要用足够数量的反引号把它们包起来。
剩余参数使用 `&` 而不是 `.`:`(defn foo [first & rest] ...)`。
Janet 不支持读取器宏,所以语法本身是固定的。如果你会读 Janet,你就能读所有的 Janet 程序。这并不意味着你能理解它们……
## Janet 更重舒适而非传统
Janet 不遵守古老的传统。`CAR` 叫做 `first`。`PROGN` 叫做 `do`。`LAMBDA` 是 `fn`,`SETQ` 是 `def`。`nil` 不是空列表;它有自己的类型,语言中有一等布尔值。它摒弃了 `EQ`、`EQL`、`EQUAL` 和 `EQUALP`。链表几乎不见踪迹。
这其实无所谓**好坏**,但我认为值得一提:如果你看到括号就以为 `FORMAT` 也不远了,那或许可以再给 Janet 一个机会。
相似文章
系统编程入门,第一部分:程序员编写程序(2025)
一篇系统编程入门文章,涵盖诸如位操作、解析、文件系统、系统调用和内存管理等基础知识,面向程序员。
@ctatedev: 引入 Zero——专为智能体设计的编程语言。我希望有一种更快、更小、更易于……
Zero 简介:一种为 AI 智能体设计的编程语言,具备显式能力、JSON 诊断和类型安全修复功能。
过去一年,我是如何改变编程方式的?你呢?
一位程序员回顾了过去一年其开发工作流程的演变,从使用基于LLM的IDE自动补全转向采用CLI编码代理和plan.md文件,并对传统IDE的必要性提出了质疑。
Launch HN: Superset (YC P26) – 面向智能体时代的IDE
Superset 是一个开源IDE,用于并行编排多个基于CLI的AI编码智能体,具有隔离的git工作树、内置监控和差异查看器。它支持多种智能体,如Claude Code、Codex CLI和Gemini CLI。
Show HN: Nibble
Nibble 是一种类 C 的系统编程语言,用 3000 行 C 代码实现,无需外部依赖或堆分配即可生成 LLVM IR。它支持 defer、递归、多种类型、结构体、指针,并包含图形演示。