Racket makes it easy to write macros that mix run-time code with various
compile-time phases, using lexical scope to refer unambiguously to the
correct binding for each identifier, including bindings that are not
directly exported. Macro-generating macros are a great example, in that
they expand to code at several different phases.

When trying to write macros that interleave documentation-time and run-time
phases, I haven't been able to find the same elegance. I'm going to
demonstrate one of the problems I've found using `#lang scribble/lp2`, but
I really encountered these issues while implementing the specification
#lang I've made for Digital Ricoeur: my current implementation is based
quite closely on `scribble/lp2`.

Imagine a library module "lib.rkt" from which you want to export a
`cross-phase-macro`, which expands to both documentation-time and run-time
code for your literate programs. Here's an initial attempt: (The code for
this example is up at
https://github.com/LiberalArtist/doc-lang-experiments/tree/1de75817866d9d782da75f1799daeb5eb26e6fe1/lp2-example1
.)
#lang at-exp racket/base

(provide cross-phase-macro)

(require (only-in racket/string string-join)
         (only-in scribble/lp2 chunk)
         syntax/parse/define
         (for-syntax racket/base))

(define (func-not-exported)
  "This is the result of func-not-exported.")

(define-syntax-parser cross-phase-macro
  [(_ <>:id)
   @#`begin{@(func-not-exported)

    @chunk[<>
           (string-join
            '("string-join is not provided"
              "at runtime"
              "by #lang scribble/lp2"))]
    }])

When you try to use `cross-phase-macro` from a literate program
"program.scrbl":
#lang scribble/lp2

@(require "lib.rkt")

@cross-phase-macro[<*>]
you get an unbound identifier error complaining about the use of
`string-join`.

The reason for the error is clear: `chunk` bodies are stitched together
into a `doc` submodule in the `racket/base` language, and `racket/base`
doesn't include `string-join`. In essence, `string-join` isn't bound at the
right phase. If this were a macro-generating macro—imagine `define-syntax`
instead of `chunk`—the solution would be equally clear: use a `for-syntax`
require or a `begin-for-syntax` block to bind `string-join` at
compile-time, and the use of the identifier in the syntax template will
resolve to the right binding, even without explicitly exporting
`string-join`.

I have yet to find a way to do the equivalent thing when mixing run-time
and documentation-time phases, rather than run-time and compile-time(s).

Some of the objectives and implementation details of `scribble/lp2` (shared
by my specification language) seem to make this problem harder. The
run-time phase shouldn't have a dependency on the documentation-time phase,
which would pull in Scribble etc. Likewise, the documentation-time phase
shouldn't import the run-time phase, other than perhaps `for-label`. The
`#%module-begin` implementation uses complicated tricks to expand the
documentation-time phase before the run-time phase, even though the
documentation-time phase becomes a `module*` submodule, because it has to
discover the run-time code (uses of `chunk` or, for my language,
`begin-for-runtime`) inside of the documentation-time code. The
implementation actually expands the documentation-time phase twice, and it
uses `strip-context` in a few places, which seems like it would undo any
fancy scope manipulation that I might otherwise look to as the beginning of
a solution.

Currently, I work around this in my specification language by manually
arranging to have the language provide any bindings I need to expand to at
run-time, but this is fragile and aesthetically displeasing. Actually, I
would say the same about expanding the documentation-time phase twice (and,
in my case, with observable differences, e.g. to get `for label` requires
working thanks to an earlier post on this list
<https://groups.google.com/d/msg/racket-users/7OrQFTOGBaw/gVlotkaYBwAJ>).
It seems like there must be a better way to do this, and I'm wondering if
anyone has ideas. With my specification language, in particular, I have the
freedom to completely change the implementation if I can get to a better
place.

Since much of this email has been about problems, I want to close by
reiterating that our specification #lang has been a big win for us at
Digital Ricoeur. While there are things about the implementation of the
#lang that I'd like to improve, using the #lang has reduced bugs, greatly
aided maintainability, and kept our prose documents in sync with various
layers of code. Part of my motivation for trying to make the implementation
more robust is that it has been so useful to us that I'd like to make the
core more reusable for others.


-Philip

-- 
You received this message because you are subscribed to the Google Groups 
"Racket Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to racket-users+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to