The snippet of code you have included in your question is written in Clojure, which is one of the many dialects of Lisp. Racket, on the other hand, is descended from Scheme, which is quite a different language from Clojure! Both have macros, yes, but the syntax is going to be a bit different between the two languages.
The Racket macro system is quite powerful, but syntax-rules
is actually a slightly simpler way to define macros. Fortunately, for this macro, syntax-rules
will suffice. A more or less direct translation of the Clojure macro to Racket would look like this:
(define-syntax-rule (for-loop [sym init check change] steps ...)
(let loop ([sym init]
[value #f])
(if check
(let ([new-value (let () steps ...)])
(loop change new-value))
value)))
It could subsequently be invoked like this:
(for-loop [i 0 (< i 10) (add1 i)]
(println i))
There are a number of changes from the Clojure code:
The Clojure example uses `
and ~
(pronounced “quasiquote” and “unquote” respectively) to “interpolate” values into the template. The syntax-rules
form performs this substitution automatically, so there is no need to explicitly perform quotation.
The Clojure example uses names that end in a hash (value#
and new-value#
) to prevent name conflicts, but Racket’s macro system is hygienic, so that sort of escaping is entirely unnecessary—identifiers bound within macros automatically live in their own scope by default.
The Clojure code uses loop
and recur
, but Racket supports tail recursion, so the translation just uses “named let
”, which is really just some extremely simple sugar for an immediately invoked lambda that calls itself.
There are a few other minor syntactic differences, such as using let
instead of do
, using ellipses instead of & steps
to mark multiple occurrences, the syntax of let
, and the use of #f
instead of nil
to represent the absence of a value.
Finally, commas are not used in the actual use of the for-loop
macro because ,
means something different in Racket. In Clojure, it is treated as whitespace, so it’s totally optional there, too, but in Racket, it would be a syntax error.
A full macro tutorial is well outside the scope of a single Stack Overflow post, though, so if you’re interested in learning more, take a look at the Macros section of the Racket guide.
It’s also worth noting that an ordinary programmer would not need to implement this sort of macro themselves, given that Racket already provides a set of very robust for
loops and comprehensions built into the language. In truth, though, they are just defined as macros themselves—there is no special magic just because they are builtins.
Racket’s for
loops do not look like traditional C-style for
loops, however, because C-style for
loops are extremely imperative. On the other hand, Scheme, and therefore Racket, tends to favor a functional style, which avoids mutation and often looks more declarative. Therefore, Racket’s loops attempt to describe higher-level iteration patterns, such as looping through a range of numbers or iterating through a list, rather than low-level semantics like describing how a value should be updated. Of course, if you really want something like that, Racket provides the do
loop, which is almost identical to the for-loop
macro defined above, albeit with some minor differences.
syntax-rules
is different fromdefmacro
in pretty significant ways, and Scheme/Racket is pretty different from Clojure. If you want, you could implement a similar concept in Racket using macros as well, but it would look a little different—Clojure and Racket are related languages, but they are not the same, and they are certainly not source compatible. However, Racket already comes with some very nice for loops, which are just plain old macros, themselves. Take a look at the docs for more info! – Alexis Kingdo
loop is almost identical to thefor-loop
macro you have mentioned in your question. However, I’ve elaborated in my answer why this construct is almost never used. – Alexis King