Many of the links in this book, including function names in code samples, lead into Racket’s browser-based documentation. For instance, here’s the entry on pairs and lists.
A copy of all Racket documentation is maintained online at docs.racket-lang.org. But Racket also builds a local copy of the documentation for every installed package.
This allows the documentation to be integrated with DrRacket without depending on a network connection. For instance, if we right-click a function name in DrRacket, the context menu will let us View documentation for that function, which will open a browser and send us to the right place.
Given everything we’ve learned so far, it shouldn’t surprise anyone to find out that Racket’s documentation is made with a domain-specific language called Scribble.
We don’t have to make our documentation with Scribble. (We don’t have to make documentation at all.) But as we saw with DrRacket, the advantage of using Scribble is that it provides a set of conveniences that are available to any Racket-implemented language:
Scribble docs are automatically integrated with DrRacket.
Scribble docs are indexed and searchable.
Scribble docs can refer to anything else in the Racket docs, and the hyperlinks will be automatically generated.
Because Scribble is a Racket dialect, it can be extended with functions and macros, just like ordinary Racket.
Scribble docs are rendered in HTML by default, but can also be rendered to PDF.
Scribble docs are the best-looking in the business. + I’m biased. I designed them. But I have had people tell me “It’s crazy, but I tried Racket just because the docs looked so good…” But they can be further customized.
The cost, however, is that using Scribble is not as easy as, say, writing a Python docstring. A Scribble documentation file is itself a program. As of today, there’s no way to automatically generate Scribble documentation from existing source code. We have to write our documentation by hand. Undeniably, it creates extra housekeeping.
But if Scribble demands a higher baseline of effort, it also raises the ceiling of what we can accomplish. We can do a lot more with Scribble than we can with the lowly docstring. After all, no software user has ever said “gosh, this documentation is just too vast, thorough, and detailed.”
In that sense, we can see Scribble partly as a policy decision about the culture of documentation Racket wants to encourage among its developers: namely, to write a lot of documentation, and make it consistent and cross-referenced. And if we look through the existing documentation—vast, thorough, and detailed—that policy is working.
Semantically, Scribble builds on top of Racket. The big difference is the syntax: rather than embedding text within S-expressions, Scribble allows us to embed S-expressions within plain text using @-expressions. Scribble supports two styles of @-expressions:
The first starts with an @ sign and a function name, optionally followed by a sequence of Racket expressions between square brackets, and then optionally ending with text between curly brackets. So this S-expression in Racket:
1 | (list 42 #f "hello world") |
Is the same as this @-expression in Scribble:
1 | @list[42 #f]{hello world} |
The curly brackets are similar to here strings in that they generate a string without requiring us to escape any characters. But they’re also more flexible than here strings, because other @-expressions can be nested inside.
We can also make an @-expression by simply prefixing any parenthesized S-expression with an @ sign. So this @-expression is also equivalent to the above list:
1 | @(list 42 #f "hello world") |
Overall, we can think of Scribble as just a more convenient way of writing Racket programs that handle a lot of text. For instance, consider this simple program:
1 | Good morning, Mr. Trevor! How are you? |
We can convert this to equivalent @-expressions like so (though we’ll omit the calls to display, because the scribble/text dialect will automatically display its results):
1 2 3 4 5 | #lang scribble/text @(define (greet . words) (format "Good morning, ~a" (string-append* words))) @(define surname "Trevor") @greet{Mr. @surname}! How are you? |
1 | Good morning, Mr. Trevor! How are you? |
We’re mixing both styles of @-expressions: the simpler style for the function and variable definitions, and the curly-brace style for the text content. But beyond that, both code fragments work the same way.
The scribble/manual dialect used for making documentation uses the same syntax as scribble/text. But it also provides a core set of functions for handling common documentation tasks. And rather than rendering the source as text, it creates a data object that’s passed to an HTML or PDF renderer.
By convention, the main Scribble documentation file for a Racket package lives in a subdirectory called scribblings and is called package-name.scrbl. So let’s create our jsonic.scrbl file now, and put #lang scribble/manual at the top to invoke the Scribble documentation dialect:
1 | #lang scribble/manual |
When we use scribble/manual, it adds two buttons to the DrRacket toolbar: Scribble PDF and Scribble HTML. We can use these buttons at any time to preview our Scribble file rendered as PDF or HTML.
In fact, let’s click the Scribble HTML button right now. We’ll see some status messages in the DrRacket REPL:
1 2 | scribble: loading xref scribble: rendering |
Then a web browser will be opened with the rendered page. This page will be blank in the main column, and have some ??? signals in the left column. That’s not surprising: no source = no result.
So let’s add some. The only three indispensable elements of documentation for a language are a title, a defmodulelang declaration, and at least one character in the body (we’ll use a sentence):
1 2 3 4 5 6 7 | #lang scribble/manual @title{jsonic: because JSON is boring} @defmodulelang[jsonic] This is a domain-specific language. |
The title of the main documentation file is the name that will show up in the table of contents. This function takes a text argument that we put between curly brackets. The title can be anything we want, though of course it’s nice to make it descriptive.
The defmodulelang declaration tells Racket that this is the starting point for the documentation for a particular language. That way, if someone searches the Racket documentation for jsonic, it will take them to this page. defmodulelang takes the name of our language as input, which is an identifier, not a string, so we put it between square brackets, not curly ones.
The body is entered as plain text.
If we save our "jsonic.scrbl" and click Scribble HTML again, we’ll now see this in our web browser:
What we’re seeing is a preview of the documentation that will be added locally when a user installs our language (and, if we upload our language to the the package server, the documentation that will be added to docs.racket-lang.org).
1 2 3 4 5 6 7 8 9 10 | #lang scribble/manual @title{jsonic: because JSON is boring} @author{Roxy Lexington} @defmodulelang[jsonic] @section{Introduction} This is a domain-specific language. |
Again, we can preview these changes by clicking Scribble HTML:
How about adding cross-references to other Racket libraries and functions? We can refer to a top-level module like json by using racketmodname, which will automatically create a hyperlinked cross-reference to the main page of the json documentation:
1 2 3 4 5 6 7 8 9 10 11 | #lang scribble/manual @title{jsonic: because JSON is boring} @author{Roxy Lexington} @defmodulelang[jsonic] @section{Introduction} This is a domain-specific language that relies on the @racketmodname[json] library. |
Which will look like this:
Inserting a cross-reference to a specific function like jsexpr->string happens in two steps. First, we wrap the term in racket, which flags it as a Racket identifier. Scribble will link this to a documentation entry if the identifier is bound, otherwise it will be typeset as plain code. To bind the identifier so Scribble can see it, we require the json module using the special for-label qualifier:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | #lang scribble/manual @(require (for-label json)) @title{jsonic: because JSON is boring} @author{Roxy Lexington} @defmodulelang[jsonic] @section{Introduction} This is a domain-specific language that relies on the @racketmodname[json] library. In particular, the @racket[jsexpr->string] function. |
The result:
Beyond that, we can document our language however we like. We’ll probably want to show some sample code and results. For that, we’ll use verbatim to print before-and-after samples:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | #lang scribble/manual @(require (for-label json)) @title{jsonic: because JSON is boring} @author{Roxy Lexington} @defmodulelang[jsonic] @section{Introduction} This is a domain-specific language that relies on the @racketmodname[json] library. In particular, the @racket[jsexpr->string] function. If we start with this: @verbatim{ #lang jsonic [ @$ 'null $@, @$ (* 6 7) $@, @$ (= 2 (+ 1 1)) $@ ] } We'll end up with this: @verbatim{ [ null, 42, true ] } |
But this time, when we click on Scribble HTML, we get an error:
1 | unexpected whitespace after @ |
The problem is that Scribble sees the @ signs within the jsonic sample code and thinks they introduce nested @-expressions. As the docs for verbatim would tell us, we can disable this assumption by using |{...}| as delimiters rather than plain {...}:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | #lang scribble/manual @(require (for-label json)) @title{jsonic: because JSON is boring} @author{Roxy Lexington} @defmodulelang[jsonic] @section{Introduction} This is a domain-specific language that relies on the @racketmodname[json] library. In particular, the @racket[jsexpr->string] function. If we start with this: @verbatim|{ #lang jsonic [ @$ 'null $@, @$ (* 6 7) $@, @$ (= 2 (+ 1 1)) $@ ] }| We'll end up with this: @verbatim{ [ null, 42, true ] } |
This time, we’ll get the right result:
Of course, in real life we’d want to elaborate further. But this will suffice for now.
When we use the Scribble HTML button to preview our documentation, it creates a number of CSS and JavaScript files in the same directory. These are needed to display the page. But they can be deleted afterward. When Scribble builds the local documentation during the package installation, it relies on shared copies of these files.
Alternatively, the project server included in the Pollen package can be used to preview Scribble source files. This can be convenient if we’re editing Scribble sources in something other than DrRacket (in which case the Scribble HTML button won’t be available).