The following module is an example of a minimal subset of Racket that allows top-level definitions, constants, and arithmetic using +
and *
:
;; min.rkt
#lang racket/base
(provide #%module-begin #%top-interaction
#%app #%datum #%top
define + *)
Here's what the provides mean:
#%module-begin
must be provided by a module for that module to be considered a "language"; it determines what a module body means. You can just reuse Racket's module-begin macro. (The #%module-begin
export gives you a hook to implement non-local constraints or transformations. For example, if you wanted to add a typechecker or check that variables are defined in alphabetical order, you could do that in the module-begin hook.)
#%top-level
is necessary for interactive languages. If you leave it out, you can't use a REPL for your language (eg, with racket -t "min.rkt" -i
).
#%app
and #%datum
make function application and self-evaluating constants (like numbers and booleans) work.
#%top
makes forward references work at the REPL, like in mutually-recursive functions. You still must define a name before you evaluate a reference to it, of course.
- The rest of the exports are the special forms and functions you want to include in your language.
Here's a program in this "min.rkt"
language:
#lang s-exp "min.rkt"
(define x 2)
(define y (+ x 5))
(* y 7)
(define (f x) (+ x x 1))
(f 8)
Note that since the language includes Racket's define
, it allows function definition, even though the language doesn't include lambda
. If you wanted a restricted version of define
, you would have to define your own macro and provide it as your language's define
, like (provide (rename-out [my-define define]))
.
You can also use rename-out
to provide Racket's cdr
as tail
, but the procedure would still print as #<procedure:cdr>
and if it raised an error the error message would still say cdr
. To change that, you'd need to define your own wrapper function that does its own error checking.