3
votes

This macro to implement a C-like for-loop in Lisp is mentioned on this page: https://softwareengineering.stackexchange.com/questions/124930/how-useful-are-lisp-macros

(defmacro for-loop [[sym init check change :as params] & steps]
  `(loop [~sym ~init value# nil]
     (if ~check
       (let [new-value# (do ~@steps)]
         (recur ~change new-value#))
       value#)))

So than one can use following in code:

(for-loop [i 0 , (< i 10) , (inc i)] 
   (println i))

How can I convert this macro to be used in Racket language?

I am trying following code:

(define-syntax (for-loop) (syntax-rules (parameterize ((sym) (init) (check) (change)) & steps)
  `(loop [~sym ~init value# nil]
     (if ~check
       (let [new-value# (do ~@steps)]
         (recur ~change new-value#))
       value#))))

But it give "bad syntax" error.

3
So, syntax-rules is different from defmacro 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 King
There are so many useful ones but not the classical one. How can we write a macro for this in Racket.rnso
I have noted in my answer how to implement this in Racket, but it’s also worth noting that the Racket do loop is almost identical to the for-loop macro you have mentioned in your question. However, I’ve elaborated in my answer why this construct is almost never used.Alexis King

3 Answers

9
votes

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:

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

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

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

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

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

5
votes

I want to expand on Alexis's excellent answer a bit. Here's an example usage that demonstrates what she means by do being almost identical to your for-loop:

(do ([i 0 (add1 i)])
    ((>= i 10) i)
  (println i))

This do expression actually expands to the following code:

(let loop ([i 0])
  (if (>= i 10)
      i
      (let ()
        (println i)
        (loop (add1 i)))))

The above version uses a named let, which is considered the conventional way to write loops in Scheme.

Racket also provides for comprehensions, also mentioned in Alexis's answer, which are also considered conventional, and here's how it'd look like:

(for ([i (in-range 10)])
  (println i))

(except that this doesn't actually return the final value of i).

3
votes

I want to rewrite on Alexis's excellent answer and Chris Jester-Young's excellent answer for people not familiar with let.

#lang racket
(define-syntax-rule (for-loop [var init check change] expr ...)
  (local [(define (loop var value)
            (if check
                (loop change (begin expr ...))
                value))]
    (loop init #f)))

(for-loop [i 0 (< i 10) (add1 i)]
          (println i))