[racket] Using `dynamic-require` for optional user-supplied "template" code?

From: Greg Hendershott (greghendershott at gmail.com)
Date: Thu May 23 14:22:08 EDT 2013

I've never had a reason to use `dynamic-require`, but now I think I do.

For my Frog static blog generator, I'd like to provide a light
"template" feature, where the user can override the default layout of
the main container <div> in the page.

I have an existing function that returns an xexpr for this. So my idea
is to let the user optionally supply a `template.rkt` file that
`provide`s a function named (say) `container` that returns (listof
xexpr?).

So I think what I want to do is something like this:

(define (container-proc)
  (define p (build-path (src-path) "template.rkt"))
  (cond [(file-exists? p)
         (dynamic-require p
                          'container
                          (thunk default-container))]
        [else default-container]))

The built-in `default-container` looks something like this:

(define (default-container bootstrap-row-class ;"row" or "row-fluid"
                           bodies              ;listof xexpr?: main content
                           tocs                ;listof xexpr?: TOC
                           tags/feeds          ;listof xexpr?: tag/feed links
                           follow)             ;listof xexpr?: Twitter, etc.
  `((div ([class ,bootstrap-row-class])
         ;; Left column
         (div ([id "left-sidebar"]
               [class "span2 bs-docs-sidebar"])
              , at tocs
              (p nbsp))
         ;; Main column
         (div ([id "content"]
               [class "span8"])
              , at bodies)
         ;; Right column
         (div ([id "right-sidebar"]
               [class "span2"])
              ,@(tags/feeds)
              ,@(follow)))))

The user-supplied template.rkt would provide some variation on it. For
example they don't want any left column with a TOC, so they supply
this:

;; template.rkt
#lang racket
(provide container)
(define (container bootstrap-row-class ;"row" or "row-fluid"
                   bodies              ;listof xexpr?: main content
                   tocs                ;listof xexpr?: TOC
                   tags/feeds          ;listof xexpr?: tag/feed links
                   follow)             ;listof xexpr?: Twitter, etc.
  `((div ([class ,bootstrap-row-class])
         ;; Don't want any left column; ignore `tocs`
         ;; Main column
         (div ([id "content"]
               [class "span9"]) ;wider
              , at bodies)
         ;; Right column: Tags/feeds/follow
         (div ([id "right-sidebar"]
               [class "span3"]) ;wider
              ,@(tags/feeds)
              ,@(follow)))))

In a quick experiment this seems to work fine. I could also go further
and make this a tiny s-exp #lang, to make the end user experience even
simpler. But meanwhile, and before that, I just want to make sure that
`dynamic-require` is a reasonable way to do this?  Does anyone
disagree?

Also: Am I correct that the user-supplied function gets JIT-ed, and
there's no big performance hit from this?  (I think the answer is no,
because from reviewing Racket's own source, I got the impression that
all modules are built on dynamic-require, but I might have
understood.)

Thank you in advance for any advice or suggestions!

Posted on the users mailing list.