Racket中的For循环宏

时间:2016-07-14 05:38:50

标签: 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#)))

所以可以在代码中使用以下内容:

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

如何将此宏转换为在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#))))

但它给出了“错误的语法”错误。

3 个答案:

答案 0 :(得分: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)))

随后可以这样调用:

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

Clojure代码有很多变化:

  1. Clojure示例使用`~(分别发音为“quasiquote”和“unquote”)将值“插入”到模板中。 syntax-rules表单会自动执行此替换,因此无需显式执行引用。

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

  3. Clojure代码使用looprecur,但是Racket支持尾递归,所以翻译只使用“named let,这实际上只是一些非常简单的糖调用自称的lambda。

  4. 还有一些其他小的语法差异,例如使用let代替do,使用省略号代替& steps来标记多次出现,{{let的语法1}},并使用#f代替nil来表示缺少值。

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

  6. 完整的宏教程远远超出了单个Stack Overflow帖子的范围,因此如果您有兴趣了解更多信息,请查看the Macros section of the Racket guide

    值得注意的是,普通的程序员不需要自己实现这种宏,因为Racket already provides a set of very robust for loops and comprehensions built into the language。事实上,它们只是被定义为宏本身 - 没有特殊的魔法,因为它们是内置的。

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

答案 1 :(得分:2)

我想稍微扩展Alexis的优秀答案。以下是一个示例用法,用于说明dofor-loop几乎完全相同的含义:

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

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

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

上面的版本使用了一个名为let的文件,它被认为是在Scheme中编写循环的传统方式。

Racket还提供了for理解,也在Alexis的答案中提到过,这些理解也被认为是传统的,以及它的外观如下:

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

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

答案 2 :(得分:2)

对于不熟悉let的人,我想重写Alexis的出色答案和Chris Jester-Young的出色答案。

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