Racket中的For循环宏

rns*_*nso 1 macros racket

此页面中提到了在Lisp中实现类似C的for循环的宏: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#)))
Run Code Online (Sandbox Code Playgroud)

因此,可以在代码中使用以下内容:

(for-loop [i 0 , (< i 10) , (inc i)] 
   (println i))
Run Code Online (Sandbox Code Playgroud)

如何将此宏转换为在Racket语言中使用?

我正在尝试以下代码:

(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#))))
Run Code Online (Sandbox Code Playgroud)

但它给出了"错误的语法"错误.

Ale*_*ing 6

您在问题中包含的代码片段是用Clojure编写的,Clojure是Lisp 的众多方言之一.另一方面,Racket是Scheme的后代,这与Clojure完全不同!两者都有宏,是的,但两种语言的语法会有所不同.

Racket宏系统非常强大,但syntax-rules实际上是一种定义宏的简单方法.幸运的是,对于这个宏,syntax-rules就足够了.将Clojure宏或多或少直接翻译为Racket将如下所示:

(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)))
Run Code Online (Sandbox Code Playgroud)

随后可以像这样调用它:

(for-loop [i 0 (< i 10) (add1 i)]
  (println i))
Run Code Online (Sandbox Code Playgroud)

Clojure代码有很多变化:

  1. Clojure示例使用`~(分别称为"quasiquote"和"unquote")将值"插入"到模板中.该syntax-rules形式自动执行这种替换,所以没有必要明确地进行报价.

  2. Clojure示例使用以散列(value#new-value#)结尾的名称来防止名称冲突,但是Racket的宏系统是卫生的,因此这种转义完全是不必要的 - 默认情况下,宏中绑定的标识符会自动存在于自己的范围内.

  3. Clojure代码使用looprecur,但是Racket支持尾递归,因此翻译只使用"命名let",这实际上只是一个非常简单的糖,用于立即调用自称的lambda.

  4. 还有其他一些次要语法上的不同,例如使用let的代替do,使用椭圆的代替& steps标记多次发生,的语法let,以及使用的#f,而不是nil以表示不存在的值.

  5. 最后,在for-loop宏的实际使用中没有使用逗号,因为,在Racket中意味着不同的东西.在Clojure中,它被视为空格,因此它也是完全可选的,但在Racket中,这将是语法错误.

但是,完整的宏教程远远超出了单个Stack Overflow帖子的范围,因此如果您有兴趣了解更多内容,请查看Racket指南的Macros部分.

值得注意的是,普通程序员本身不需要实现这种宏,因为Racket已经提供了一组非常强大的for循环和内置于语言中的理解.事实上,它们只是被定义为宏本身 - 没有特殊的魔法只因为它们是内置的.

然而,Racket的for循环看起来不像传统的C风格for循环,因为C风格的for循环非常必要.另一方面,Scheme和Racket倾向于支持功能样式,这避免了变异并且通常看起来更具说明性.因此,Racket的循环尝试描述更高级别的迭代模式,例如循环遍历一系列数字或迭代列表,而不是描述如何更新值的低级语义.当然,如果你真的想要这样的话,那么Racket提供的do循环几乎与for-loop上面定义的宏相同,尽管有一些细微的差别.

  • @ChrisJester-Young因为`let`允许内部定义,而`begin`则不允许.有时`begin`是优选的,因为它是如何拼接到周围的上下文中(它没有引入新的范围),但在这种情况下,没有上下文可以拼接,所以`let`总是最好的选择. (3认同)

Chr*_*ung 5

我想稍微扩展一下亚历克西斯的出色回答。这是一个示例用法,通过do与您的几乎相同来演示她的含义for-loop

(do ([i 0 (add1 i)])
    ((>= i 10) i)
  (println i))
Run Code Online (Sandbox Code Playgroud)

这个do表达式实际上扩展为以下代码:

(let loop ([i 0])
  (if (>= i 10)
      i
      (let ()
        (println i)
        (loop (add1 i)))))
Run Code Online (Sandbox Code Playgroud)

上面的版本使用了 named let,这被认为是在 Scheme 中编写循环的常规方式。

Racket 还提供了for理解,也在亚历克西斯的回答中提到,这也被认为是传统的,这是它的样子:

(for ([i (in-range 10)])
  (println i))
Run Code Online (Sandbox Code Playgroud)

(除了这实际上并不返回 的最终值i)。