← 返回信息流
AI 资讯Hacker News·2 天前

为何选择 Janet

原标题:Why Janet?

速览

本文分析了选择 Janet 的背景和理由。内容涉及对该技术或产品的评估。旨在说明其优势。

AI 深度解读

为什么选择 Janet?—— 一位资深开发者的深度推荐

背景

在编程语言的浩瀚海洋中,Lisp 家族以其独特的括号语法和强大的宏系统著称,但也常因学习曲线陡峭而让初学者望而却步。然而,对于许多寻求“有趣侧边项目”(fun side projects)语言的开发者而言,Janet 正在成为一个意想不到的热门选择。

Janet 是一个轻量级的 Lisp 方言,旨在结合简单性、高性能和可嵌入性。尽管其语法中充满了括号——这在当今以简洁著称的编程趋势中显得颇为“复古”——但作者认为,Janet 凭借其独特的设计哲学,在多个维度上提供了卓越的开发体验。作者不仅自己深度使用 Janet,还撰写了一本名为《Janet for Mortals》(面向凡人的 Janet)的免费电子书,旨在吸引更多开发者加入 Janet 社区(Janetors)。

这篇文章并非简单的功能罗列,而是一份基于实战经验的“销售演讲”,旨在说服那些对传统脚本语言(如 Perl、Bash)或嵌入式语言(如 Lua)感到疲惫的开发者,给 Janet 一个机会。

核心内容

Janet 的核心优势体现在其极简的核心设计、强大的分发能力、卓越的文本处理能力、独特的子进程 DSL、易于嵌入的特性、灵活的数据结构以及极具表现力的宏系统。

极简的核心与快速上手

Janet 是一门命令式语言,支持一等公民函数、单一命名空间和词法块作用域。其核心极其精简,仅包含八条指令:dodefvarsetifwhilebreakfn。尽管核心简单,但通过宏系统,开发者可以构建出功能强大且便捷的高级控制流结构。

Janet 的学习曲线非常平缓。其运行时语义与 JavaScript 类似,但拥有更明确的值类型,且避免了 JavaScript 中许多令人困惑的陷阱(wats)。整个标准库的内容甚至可以在一页纸上展示完毕。这种“下午即可入门”的易用性是作者最初被吸引的关键。

卓越的可分发性

Janet 程序可以编译为原生可执行文件,并静态链接 Janet 运行时。这意味着你可以将程序分享给他人,而无需对方安装 Janet 或任何依赖项。甚至,接收者可能根本不知道该程序是用 Janet 编写的。

其实现机制优雅而高效:Janet 首先将代码编译为字节码,然后将字节码嵌入到一个启动 Janet 运行时的 .c 文件中,最后使用系统的 C 编译器进行编译。由于 Janet 设计初衷即为易于嵌入,这种“自我嵌入”的方式顺理成章。

编译后的“Hello World”程序体积极小(在 aarch64 macOS 上约为 784KB),其中包含了完整的运行时、垃圾回收器甚至字节码编译器。这使得 Janet 成为编写命令行工具的理想选择,因为它允许程序在运行时评估 Janet 代码,具备极高的灵活性。

超越正则表达式的文本解析

Janet 摒弃了传统的正则表达式,转而基于解析表达式语法(Parsing Expression Grammars, PEGs)进行文本处理。PEGs 比正则表达式更简单、更强大且更可预测。它们不是面向行的,因此能轻松处理多行文本;它们能够解析 HTML、JSON 等非正则语言,甚至能处理包含任意空字节的二进制文件格式。

Janet 的解析器是结构化的、可组合的、一等公民的对象,且易于学习。

顶级的子进程 DSL

Janet 拥有所有高级语言中最好的子进程领域特定语言(DSL)。通过第三方库 sh,开发者可以直接在 Janet 代码中表达管道和重定向操作,例如:

($ find . -name *.janet | say)

这种 DSL 如此出色,以至于作者专门在书中为其开辟了一章。它使得 Janet 不仅成为 Perl 的合理替代方案,更成为 Bash 在广泛场景下的有力竞争者。

易于嵌入

虽然 Lua 已成为事实上的“嵌入式语言”,但 Janet 提供了同样甚至更优的体验。Janet 运行时是一个小型的 C 库,只需链接并调用常规 C 函数即可操作 Janet 值。这种特性使得在 Web 网站中嵌入 Janet,并编写带有自定义可编程 DSL 的静态站点成为可能。

可变与不可变集合

Janet 的集合类型同时支持可变和不可变版本。

  • 不可变集合具有值语义:例如 [1 2](take 2 [1 2 3]) 在值上是不可区分的,尽管它们的内存地址不同。
  • 可变集合具有引用语义:例如 @{:x 1 :y 2} 仅等于其自身。具有相同键值对的其他哈希表被视为不同的对象。

许多语言并未在标准库中内置不可变复合值,而 Janet 提供了这一现代编程特性。

宏的力量

宏是 Janet 最强大的特性,也是作者认为开发者应该学习 Janet 的真正原因。

  • 双重思维模式:编写宏需要同时思考两个层面的代码:当前编译时运行的代码(操作值和抽象语法树)以及未来运行时执行的代码(生成的应用代码)。
  • 非卫生宏与引用透明性:Janet 的宏不是卫生的(hygienic),且没有单独的函数命名空间。但通过允许取消引用字面量函数,Janet 使得编写完全引用透明的宏成为可能。这是一种解决复杂问题的简单而优雅的方案。

从编译时到运行时的值传递

这是 Janet 最有趣且常被忽视的特性。任何 Janet 值都可以隐式地序列化到磁盘并在稍后读取。

  • 隐式序列化:当编译 Janet 程序时,它会执行所有顶层指令(语句、函数声明等)。执行完毕后,Janet 会将程序的当前状态快照写入磁盘。
  • 完整状态保留:这个快照保留了共享引用,因此可变值在“恢复”快照后仍可被修改。生成器(Generators)和闭包(Closures)的状态也能被完美保留。
  • 无需宏的超能力:即使不使用宏,开发者也可以利用这一特性。例如,在游戏开发中预计算样条曲线,或在编译时读取文件以将资源嵌入最终二进制文件中,甚至执行任意副作用。作者举了一个例子:基于 SQL 模式文件自动生成数据库绑定,这在大多数语言中实现起来非常困难。

手感极佳的语言设计

Janet 的语法在简单性、统一性和多样性之间取得了完美平衡:

  • 括号与分隔符:虽然广泛使用括号,但通过 [] 表示列表,{} 表示表(tables),打破了视觉上的单调。
  • 可变字面量:始终以前缀 @ 标记可变字面量,如 @"mutable string"{:immutable hash-table}
  • 匿名函数:标准写法为 (fn [x] (+ 1 x)),但也提供了简写符号 |,如 |(+ 1 $),可将任意表达式提升为函数。
  • 展开操作:支持使用 ; 进行“splats”或“spreads”操作,如 (+ ;args)
  • 字符串字面量:支持任意数量的反引号,闭合时数量需一致。反引号内的字符串不解析转义序列(如 \n),从而可以创建包含任意内容的字符串,无需担心转义问题。

关键要点

  • 极简核心:Janet 仅由 8 条核心指令组成,标准库极简,学习成本低,适合快速原型开发。
  • 独立分发:编译后的二进制文件体积小(<1MB),静态链接运行时,无需依赖环境即可跨平台运行。
  • 强大的文本处理:基于 PEGs 的解析器比正则表达式更强大、更可预测,支持多行文本和二进制文件解析。
  • 优秀的 Shell 替代方案:通过 sh 库提供的 DSL,Janet 在系统脚本编写方面具备与 Bash 竞争的能力。
  • 嵌入式友好:作为小型 C 库提供,易于嵌入到 C/C++ 项目或 Web 应用中。
  • 现代数据结构:原生支持可变和不可变集合
查看原文 →ianthehenry.com