Languages as Dotfiles
posted by Leif Andersen and Ben Greenman
Tired of writing (require (for-syntax syntax/parse))
at the top of your Racket programs? This post shows how to make a #lang
to customize your default programming environment.
Let’s build a language #lang scratch
that:
-
loads the
racket/base
,racket/format
,racket/list
, andsyntax/parse
(at phase 1) libraries; and -
enables Scribble’s @-syntax.
We’ll follow a three-step recipe:
-
build an empty
scratch
library, -
load the libraries, and
-
change the reader.
At the end, we’ll see how to make scratch
your default language in DrRacket.
Getting Started
First we need to make a scratch/
directory with info.rkt
and main.rkt
files:
$ mkdir scratch; cd scratch $ touch info.rkt $ touch main.rkt
Inside the info.rkt
file, write:
Inside the main.rkt
file, write:
#lang racket/base
Now from inside the scratch/
directory, install the package:
$ raco pkg install
You are now the proud parent of a new Racket package.
raco-pkg-new is a shortcut for starting a new package.
For more information on the
info.rkt
file format, see theraco
documentation.
Combining Libraries
Any program can now (require scratch)
to import all bindings provided by the main.rkt
file. Our next step is to reprovide bindings from other libraries in main.rkt
.
Since we want to use scratch
as a language, we also need to specify how to read a scratch
program. The syntax/module-reader
language provides a shorthand for doing so.
Here is the updated main.rkt
file:
#lang racket/base (require racket/format racket/list (for-syntax racket/base syntax/parse)) (provide (all-from-out racket/base racket/format racket/list) (for-syntax (all-from-out racket/base syntax/parse))) (module* reader syntax/module-reader scratch)
The provide
form declares the exports of the scratch
module. In other words, if another module contains the form (require scratch)
then that module will import bindings from racket/base
, racket/format
, racket/list
, and syntax/parse
.
The reader
submodule is written in the syntax/module-reader
language. This submodule imports all bindings from its enclosing module (scratch
, or to be slightly more precise “the toplevel module in the file scratch/main.rkt
”) and defines a language that provides those bindings and uses the reader from racket/base
.
In short, this code does what we want.
#lang scratch (define-syntax (did-it-work? stx) (syntax-parse stx [_ #'(first '(yes it did))])) (did-it-work?)
Yes it does.
Annoyed that the
require
andprovide
forms are so similar? There’s a library for that.
Changing the Reader
Next, we want to enable the @-expression reader. This involves rexporting the scribble read
and read-syntax
functions in the reader
submodule in main.rkt
:
#lang racket/base (require racket/list (for-syntax racket/base syntax/parse)) (provide (all-from-out racket/list racket/base) (for-syntax (all-from-out racket/base syntax/parse))) (module* reader syntax/module-reader scratch #:read s:read #:read-syntax s:read-syntax (require (prefix-in s: scribble/reader)))
To test that it works, let’s embed some C syntax in our Racket program:
#lang scratch (define-syntax (did-it-work? stx) (syntax-parse stx [_ #'(first '(yes it did))])) (did-it-work?) @~a{ int main() { return 0; }}
At this point, running
$ raco setup --check-pkg-deps scratch
will report an undeclared dependency onat-exp-lib
. Make sure to addat-exp-lib
to thedeps
list in yourinfo.rkt
file, or run$ raco setup --fix-pkg-deps scratch
Using
prefix-in
is not necessary; it just clarifies whereread
andread-syntax
come from.
If you think inline C strings are interesting, you should definitely watch Jay McCarthy’s RacketCon 2016 talk on remix.
DrRacket’s Automatic #lang
Line
To make scratch
the default language for new files in DrRacket:
-
Click “Language” in the menu bar.
-
Click “Choose Language” in the drop-down menu.
-
Click the radio button for “The Racket Language”, then click the “Show Details” button at the bottom of the window.
-
Type
#lang scratch
in the text box labeled “Automatic#lang
line”.
Click “Ok”, and that’s the end. Enjoy.
The End
You can and should engineer the #lang
line of your Racket programs to remove unnecessary boilerplate and/or enforce a project-specific development environment.
Notes:
-
Feel free to pubish your custom language on the Racket package server. (Make sure to run
$ raco setup --check-pkg-deps scratch
beforehand.) -
Our personal “dotfiles” are racket-scratch and agile.
-
The title “Languages as Dotfiles” is a reference to Languages as Libraries