Why Janet? (2023)

Why Janet? (2023)

April 12, 2023 Why Janet? I never thought it could happen to me. I mean, parentheses? In this day and age? But for the past couple years, my go-to programming language for fun side projects has been a little Lisp dialect called Janet. (print “hey janet”) I like Janet so much that I wrote an entire book about it, and put it on The Internet for free, in the hopes of attracting more Janetors to the language. I think you should read it, but I know that you don’t believe me, so I’m going to try to convince you. Here’s my attempt at a sales pitch: here is why you – you of all people – should give Janet a chance.

2023年4月12日,为什么是 Janet?我从未想过这种事会发生在我身上。我是说,括号?在这个年代?但在过去几年里,我进行趣味副业项目的首选编程语言,是一种名为 Janet 的小型 Lisp 方言。(print “hey janet”) 我非常喜欢 Janet,以至于我专门为它写了一整本书,并免费发布在互联网上,希望能吸引更多的“Janetors”加入。我认为你应该读读它,但我知道你可能不信,所以我打算试着说服你。这是我的推销词:为什么你——没错,就是你——应该给 Janet 一个机会。

Janet is simple

Janet is an imperative language with first-class functions, a single namespace for identifiers, and lexical block scoping. The core of the language is very small, consisting of only eight instructions: do, def, var, set, if, while, break, fn. But thanks to macros, there are lots of high-level wrappers that give you more powerful or convenient control flow. You can “learn” Janet in an afternoon, because the runtime semantics are extremely familiar: think JavaScript, plus value types, minus all the wats. And the rest of the language is small: the entire standard library fits on one page. It was this ease of getting started that got me hooked in the first place.

Janet 很简单

Janet 是一种命令式语言,具有一等函数、单一标识符命名空间和词法块作用域。该语言的核心非常精简,仅包含八条指令:do、def、var、set、if、while、break、fn。但得益于宏(macros),它拥有许多高级封装,能为你提供更强大或更便捷的控制流。你可以在一个下午就“学会”Janet,因为其运行时语义非常熟悉:想象一下 JavaScript,加上值类型,再减去那些令人困惑的怪癖。此外,该语言的其他部分也很小巧:整个标准库在一页纸内就能写完。正是这种易于上手的特性,让我一开始就迷上了它。

Janet is distributable

It’s easy to compile Janet programs into native executables that statically link the Janet runtime. And you can share those programs with other people, without asking them to install Janet first – or your project’s dependencies, or anything else for that matter. You don’t even have to tell them it’s written in Janet! The way that Janet pulls this off is very elegant: Janet compiles itself to bytecode, and then writes that bytecode into a .c file that also starts up the Janet runtime. Then it compiles that C file with your system’s C compiler. Since Janet is designed to be easy to embed, this makes perfect sense: it is, essentially, embedding itself into a trivial C executable. A simple Janet “hello world” compiled to a native binary weighs under a megabyte (784K for Janet 1.27.0 on aarch64 macOS, but your mileage may vary). This includes the full Janet runtime, garbage collector, and even the bytecode compiler – so you can write programs that evaluate Janet code at runtime, if you want to. This makes Janet an excellent choice for writing little command-line apps. Which is especially true when you consider that…

Janet 可分发

将 Janet 程序编译成静态链接了 Janet 运行时的原生可执行文件非常容易。你可以与他人分享这些程序,而无需让他们先安装 Janet,也不需要安装项目的依赖项或其他任何东西。你甚至不需要告诉他们这是用 Janet 写的!Janet 实现这一点的方式非常优雅:它将自身编译为字节码,然后将该字节码写入一个同时启动 Janet 运行时的 .c 文件中,接着使用系统的 C 编译器编译该 C 文件。由于 Janet 被设计为易于嵌入,这非常合理:本质上,它将自身嵌入到了一个简单的 C 可执行文件中。一个编译为原生二进制文件的简单 Janet “hello world”程序大小不到 1MB(在 aarch64 macOS 上 Janet 1.27.0 为 784K,具体取决于环境)。这包含了完整的 Janet 运行时、垃圾回收器,甚至还有字节码编译器——因此,如果你愿意,甚至可以编写在运行时评估 Janet 代码的程序。这使得 Janet 成为编写小型命令行应用的绝佳选择。当你考虑到这一点时,尤其如此……

Janet is unrealistically good at parsing text

Instead of regular expressions, Janet’s text wrangling is based around parsing expression grammars. Parsing expression grammars are simpler, more powerful, and more predictable than regular expressions. They aren’t line-oriented, so they can parse multi-line text without a problem. They can also parse HTML, or JSON, or any other non-regular language. They can also parse binary file formats – they have no problems with arbitrary null bytes. They really are parsers: structured, composable, first-class parsers. And they’re pretty easy to learn!

Janet 在文本解析方面强得离谱

Janet 处理文本不是基于正则表达式,而是基于解析表达式语法(PEG)。PEG 比正则表达式更简单、更强大且更具可预测性。它们不是面向行的,因此可以毫无问题地解析多行文本。它们还可以解析 HTML、JSON 或任何其他非正则语言。它们甚至能解析二进制文件格式——处理任意空字节毫无压力。它们确实是解析器:结构化、可组合、一等公民式的解析器。而且它们非常容易学习!

Janet has the best subprocess DSL of any high-level language

There is a third-party library called sh that provides a shell scripting DSL that allows you to express pipes and redirects directly in Janet. Like this: ($ find . -name *.janet | say) It’s pretty incredible. It’s such a nice DSL that I dedicated a whole chapter of Janet for Mortals to it – and the things that you can do with it. It elevates Janet from a reasonable alternative to Perl to a reasonable alternative to Bash for a surprisingly large range of programs.

Janet 拥有高级语言中最好的子进程 DSL

有一个名为 sh 的第三方库,它提供了一种 shell 脚本 DSL,允许你在 Janet 中直接表达管道和重定向。就像这样:($ find . -name *.janet | say) 这简直不可思议。这个 DSL 非常棒,我专门在《Janet for Mortals》中用了一整章来介绍它以及你能用它做的事情。它将 Janet 从 Perl 的一个合理替代品,提升到了在相当广泛的程序范围内成为 Bash 的合理替代品。

Janet is embeddable

Lua has become the de facto “embedded language,” which is a shame, because… well, this isn’t a post about Lua. You might not care about this very much, but there’s a chance that it’s just because you haven’t tried it yet: being able to write programs that expose scripting interfaces is a pretty fun superpower. Embedding Janet is very easy: the Janet runtime is a small C library, and all you have to do is link it in and then call regular C functions to manipulate Janet values. You can even embed it into websites, and write static sites with custom programmable DSLs!

Janet 可嵌入

Lua 已经成为事实上的“嵌入式语言”,这很遗憾,因为……好吧,这不是一篇关于 Lua 的文章。你可能对此不太关心,但这很可能是因为你还没尝试过:能够编写暴露脚本接口的程序是一种非常有趣的超能力。嵌入 Janet 非常简单:Janet 运行时是一个小型 C 库,你只需将其链接进去,然后调用常规的 C 函数来操作 Janet 值即可。你甚至可以将它嵌入到网站中,并用自定义的可编程 DSL 编写静态网站!

Janet has mutable and immutable collections

Janet’s collection types come in mutable and immutable flavors. Immutable collections have value semantics: the immutable vector [1 2] is indistinguishable from (take 2 [1 2 3]), despite the fact that they have different memory addresses. Mutable collections, on the other hand, have reference semantics: the hash table @{ :x 1 :y 2 } is only equal to itself. Another hash table with the same keys and values is a distinct object. Not every language has immutable composite values built right into the standard library!

Janet 拥有可变和不可变集合

Janet 的集合类型分为可变和不可变两种。不可变集合具有值语义:不可变向量 [1 2](take 2 [1 2 3]) 是无法区分的,尽管它们的内存地址不同。另一方面,可变集合具有引用语义:哈希表 @{ :x 1 :y 2 } 只等于它自身。另一个具有相同键和值的哈希表是一个不同的对象。并非每种语言都在标准库中内置了不可变复合值!

Macros, macros, macros

I think this is the real reason you should learn Janet, but I didn’t want to lead with it because I didn’t want to scare you off. You can write Janet just fine without ever learning how to write macros. But you should learn how, because writing macros is fun. It feels different than any sort of programming that I’ve done before. Writing macros requires thinking twice at once: you’re writing code to write code, so you have to keep two threads of execution straight in your mind: the code that is running now, at compile time, manipulating values and abstract syntax trees, and the code that you are manipulating, the application code that you produce, the code that will run in the future. Janet’s macros are not hygienic, and Janet does not have a separate namespace for functions. But by allowing you to unquote literal functions, Janet makes it possible to write macros that are completely referentially transparent. It’s an incredibly simple and elegant solution to an otherwise very delicate problem. And the fact that it is possible to do this in Janet highlights my next favorite feature…

宏,宏,宏

我认为这是你应该学习 Janet 的真正原因,但我不想一开始就提它,因为我不想把你吓跑。即使完全不学如何写宏,你也能很好地使用 Janet。但你应该学学,因为写宏很有趣。它与我之前做过的任何编程感觉都不同。编写宏需要同时进行双重思考:你是在写代码来写代码,所以你必须在脑海中理清两条执行线程:一条是现在(编译时)正在运行的代码,它在操作值和抽象语法树;另一条是你正在操作的代码,即你生成的应用程序代码,也就是未来会运行的代码。Janet 的宏不是卫生的(hygienic),且 Janet 没有为函数设置单独的命名空间。但通过允许取消引用(unquote)字面函数,Janet 使得编写完全引用透明的宏成为可能。这是一个极其简单且优雅的解决方案,解决了原本非常棘手的问题。而 Janet 能做到这一点,也突显了我最喜欢的下一个特性……

Janet lets you pass values from compile-time to run-time

This is the most interesting thing about Janet, in my opinion. But it might not sound very inte…

Janet 允许你将值从编译时传递到运行时

在我看来,这是 Janet 最有趣的地方。但这听起来可能不太……