If I understand correctly, originally you had something like this:
(define (root)
"/")
(define (render-template path)
(displayln path))
(render-template (build-path (root) "templates" "index.jinja"))
;; => /templates/index.jinja
What I suggest is simply changing render-template
so that it may be
called with multiple path "parts" -- and it handles calling
build-path
for you:
(define (render-template . path-parts)
(define path (apply build-path path-parts))
(displayln path))
Now you can call it like this:
(render-template (root) "templates" "index.jinja")
;; => /templates/index.jinja
Incidentally, calling it the original way still works, because
build-path
will act as identity in this case:
(render-template (build-path (root) "templates" "index.jinja"))
;; => /templates/index.jinja
I think this is the most "Rackety" way. One nice thing about
s-expressions is you don't have to type "separators" like ,
or /
.
Spaces suffice. And I think the more you read and write Racket code,
the more you'll feel this way.
Granted, perhaps the most Rackety thing of all is the ability to make
your own little (or big) languages. If you wanted a "DSL" to write
files consisting primarily of paths, that might be one thing. But in
this case I'm not sure I see the big win.
If anything, maybe you want just a macro
that makes /
act as whitespace in this context. i.e. To
make /
mean the "nothing" that it ultimately needs to be, in the expanded code.
For example:
#lang racket/base
(require (for-syntax racket/base syntax/parse))
(define-syntax (render-template stx)
(define-splicing-syntax-class pp
(pattern (~seq part (~optional (~literal /)))))
(syntax-parse stx
[(_ p:pp ...) #'(do-render-template p.part ...)]))
(define (do-render-template . path-parts)
(define path (apply build-path path-parts))
(displayln path))
(render-template (root) / "templates" / "index.jinja")
;; => /templates/index.jinja
(render-template (root) "templates" "index.jinja")
;; => /templates/index.jinja
Note that the /
are completely optional here. They get treated as
whitespace.
Also note that the real work is done in the function, renamed now to do-render-template
. The macro is just a wrapper. Generally it's best for macros to do as little work as necessary, and for things that can be functions to be functions.
But again, personally I wouldn't bother with the macro, I'd go with the approach I suggested above.
Update: As a p.s., if I understand correctly, Python's PathLib.Path
is defining /
as an operator? Well, Racket doesn't really have "operators". It has functions. And the math functions like /
accept any number of arguments. So instead of 10 / 5 / 2
we write (/ 10 5 2)
. Which, really, brings us full-circle back to build-path
: A function that takes any number of path parts.
I suppose you could effectively rename build-path
to /
:
(require (rename-in (except-in racket /)
[build-path /]))
(/ (root) "templates" "index.jinja")
But this isn't really operator overloading, because these are plain functions not methods. And... I wouldn't do it. :)