1

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))))
11
  • perhaps you could add some explanations about what loop/recur macro is and give some very simple example of how it looks and what it does? Commented Apr 22 at 10:21
  • Will hopefully have time to type up a real answer tomorrow; TLDR: Syntax parameter for recur. The alternating id and initial value bit without wrapping each pair in parens is the tough part; that doesn't lend itself well to syntax patterns. Commented Apr 22 at 10:21
  • @WillNess the code for the macro usage was included in the question. This is one of the fundamental features of Clojure. It's like asking to explain what for loop is and what it does. But I included the explanation, I hope it will help you. Commented Apr 22 at 14:13
  • @jcubic FOR loop concept is shared by many languages and is known for decades. if you don't want input from Scheme-knowledgeable people unfamiliar with the specifics of Clojure, that's your choice. but these Q&As are supposed to serve wider audience, not just the asker. many a reader will be familiar with FOR yet unfamiliar with Clojure's loop/recur, and those are who I had in mind when I asked you for the clarification. Commented Apr 22 at 16:42
  • I've edited with a clearer example. please check that I didn't make any errors. :) Commented Apr 22 at 16:47

1 Answer 1

1

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.)

Sign up to request clarification or add additional context in comments.

4 Comments

I'm trying to execute your syntax-case solution with this code (loop (i 0 j 10) (if (< i 10) (begin (display i) (display " ") (display j) (newline) (recur (+ i 1) (- j 1))))) but got an error: syntax-parameterize: invalid syntax parameter in subform recur of
@jcubic did you remember to run the first command that creates the syntax parameter?
Yea, with the syntax param it works. Thanks. I edited your post, so the example is self-contained.
I figured it was obvious you had to include both parts.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.