I'm learning syntax-case
macro system. This is what I have so far:
(define-syntax loop
(lambda (x)
(syntax-case x ()
[(_ (var val x ...) body)
(with-syntax ([recur (datum->syntax #'var 'recur)])
#'(let recur ((var val) x ...)
body))])))
(loop [i 0 j 10]
(if (< i 10)
(begin
(display i)
(display " ")
(display j)
(newline)
(recur (+ i 1) (- j 1)))))
The problem is the binding in let
. The above code creates
[(i 0) j 10]
and when I change the code into:
(define-syntax loop
(lambda (x)
(syntax-case x ()
[(_ (var val ...) body)
(with-syntax ([recur (datum->syntax #'var 'recur)])
#'(let recur ((var val) ...)
body))])))
I get:
[(i 0) (i j) (i 10)]
I think that I need to use recursive macro, but I'm not sure how to do this with syntax-case
.
EDIT: here is an explanation of loop
macro in Clojure if you're not familiar with it. It works like named let
with the name recur
that calls the loop recursively. But the list of variables and values is a flat list instead of list of pairs.
Clojure's loop
:
(loop [i 10 j 20]
(if (> i 0)
(recur (- i 1) (- j 2))))
Scheme's named let
:
(let recur ((i 10)
(j 20))
(if (> i 0)
(recur (- i 1) (- j 2))))
There are two parts to this - implementing the recur
form, and handling the alternating identifier and initial value variable bindings.
With guile (And other schemes that support the construct (codified in SRFI-139)), the best way to do the former is with a syntax-parameter
. Using with-syntax
to inject a new fixed binding name has potential issues; syntax parameters fix them. See this paper, "Keeping it Clean with Syntax Parameters" for details.
Converting from Clojure style (var1 init1 var2 init2 ...)
to Scheme let
style ((var1 init1) (var2 init2) ...)
can be done with a recursive macro that converts a pair at a time; when combined with using syntax parameters, a Scheme version of Clojure's loop
macro can be written using syntax-rules
; no need for syntax-case
.
The following uses a single macro by both matching the base loop
syntax and an alternative one that's used to do the transformation (I picked this trick up from some of the more hairy macro intensive SRFI reference implementations).
(define-syntax-parameter recur
(lambda (stx)
(syntax-violation 'recur "recur used outside of loop" stx)))
(define-syntax loop
(syntax-rules (internal)
((_ internal ((var init) ...) () body ...)
(let rekur ((var init) ...)
(syntax-parameterize ((recur (identifier-syntax rekur)))
body ...)))
((_ internal ((var init) ...) (next-var next-init rest ...) body ...)
(loop internal ((var init) ... (next-var next-init)) (rest ...) body ...))
((_ (vars+inits ...) body ...) ; public syntax form
(loop internal () (vars+inits ...) body ...))))
(identifier-syntax
creates a new syntax transformer that can be used to map use of one identifier to another.)
With this macro, you can do things like:
scheme@(guile-user)> (load "./loop.scm")
scheme@(guile-user)> (loop [iter 1 acc 0] ; the guile reader accepts [] as an alternative to ()
(if (> iter 10)
acc
(recur (1+ iter) (+ acc iter))))
$1 = 55
If you do want to use syntax-case
, you can treat the list of variables and initial values as, well, a list, and transform it into the form Scheme let
expects in a unsyntax
expression:
(use-modules (ice-9 match))
(define-syntax-parameter recur
(lambda (stx)
(syntax-violation 'recur "recur used outside of loop" stx)))
(define-syntax loop
(lambda (stx)
(syntax-case stx ()
((_ (vars+inits ...) body ...)
#`(let rekur
#,(let rep ((results '())
(vars+inits #'(vars+inits ...)))
(match vars+inits
(() (reverse! results))
((var init . rest)
(rep (cons (list var init) results) rest))))
(syntax-parameterize ((recur (identifier-syntax rekur)))
body ...))))))
(This ends up looking a lot like a traditional Common Lisp style macro, but preserving hygiene and syntax data.)