定义 Racket 的一个子集

Define a subset of Racket

由于 Racket 以其创建新编程语言的能力而闻名,因此以下内容应该不会太难。

我想创建一个 Racket 的子集(我们将其命名为 min)用于教育目的,重命名某些功能(tail 而不是 cdr)并忽略其他功能(例如因为 string=?= 只有 equal?).

我做的第一步是用 #lang s-exp "min.rkt" 启动我的 min 文件,但我卡在了扩展器阶段。

以下模块是 Racket 的最小子集的示例,它允许使用 +* 的 top-level 定义、常量和算术:

;; min.rkt
#lang racket/base
(provide #%module-begin #%top-interaction
         #%app #%datum #%top
         define + *)

提供的含义如下:

  • #%module-begin 必须 由一个模块提供,该模块被认为是一种“语言”;它决定了模块主体的含义。你可以重用 Racket 的 module-begin 宏。 (#%module-begin 导出为您提供了一个钩子来实现 non-local 约束或转换。例如,如果您想添加类型检查器或检查变量是否定义在按字母顺序排列,您可以在 module-begin 挂钩中执行此操作。)
  • #%top-level 是交互式语言所必需的。如果你遗漏了它,你就不能为你的语言使用 REPL(例如,racket -t "min.rkt" -i)。
  • #%app#%datum 使函数应用程序和 self-evaluating 常量(如数字和布尔值)起作用。
  • #%top 使前向引用在 REPL 中起作用,就像在 mutually-recursive 函数中一样。当然,在评估对它的引用之前,您仍然必须定义一个名称。
  • 其余的导出是您希望包含在您的语言中的特殊形式和函数。

这是使用这种 "min.rkt" 语言的程序:

#lang s-exp "min.rkt"
(define x 2)
(define y (+ x 5))
(* y 7)
(define (f x) (+ x x 1))
(f 8)

请注意,由于该语言包含 Racket 的 define,它允许函数定义,即使该语言不包含 lambda。如果您想要 define 的受限版本,则必须定义自己的宏并将其提供为您的语言的 define,例如 (provide (rename-out [my-define define])).

您还可以使用 rename-out 将 Racket 的 cdr 提供为 tail,但该过程仍将打印为 #<procedure:cdr>,如果它引发错误,则错误消息仍然会说 cdr。要更改它,您需要定义自己的包装函数来进行自己的错误检查。