Macros 球拍中的For循环宏

Macros 球拍中的For循环宏,macros,racket,Macros,Racket,本页提到了在Lisp中实现类似C的for循环的宏: 因此,不止一个人可以在代码中使用以下内容: (for-loop [i 0 , (< i 10) , (inc i)] (println i)) 但它给出了“糟糕的语法”错误 问题中包含的代码片段是用Clojure编写的,Clojure是Lisp的多种方言之一。另一方面,Racket是Scheme的后代,Scheme与Clojure的语言截然不同!这两种语言都有宏,是的,但是这两种语言的语法会有点不同 Racket宏系统非常强大,

本页提到了在Lisp中实现类似C的for循环的宏:

因此,不止一个人可以在代码中使用以下内容:

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

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

问题中包含的代码片段是用Clojure编写的,Clojure是Lisp的多种方言之一。另一方面,Racket是Scheme的后代,Scheme与Clojure的语言截然不同!这两种语言都有宏,是的,但是这两种语言的语法会有点不同

Racket宏系统非常强大,但是
语法规则
实际上是定义宏的一种稍微简单的方法。幸运的是,对于这个宏,
语法规则
就足够了。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))
随后可以这样调用它:

(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))
(用于循环[i0(
Clojure代码有许多变化:

  • Clojure示例使用
    `
    ~
    (分别发音为“quasikote”和“unquote”)将值“插值”到模板中。
    语法规则
    表单自动执行此替换,因此无需显式执行引号

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

  • Clojure代码使用
    循环
    递归
    ,但Racket支持尾部递归,因此翻译只使用,这对于立即调用的lambda来说是一种非常简单的糖分,它调用自身

  • 还有一些其他较小的语法差异,例如使用
    let
    而不是
    do
    ,使用省略号而不是
    &steps
    来标记多次出现,使用
    let
    的语法,以及使用
    #f
    而不是
    nil
    来表示没有值

  • 最后,在实际使用
    for loop
    宏时不使用逗号,因为
    在Racket中的含义不同。在Clojure中,它被视为空白,因此在那里它也是完全可选的,但在Racket中,这将是一个语法错误

  • 不过,完整的宏教程远远超出了单堆栈溢出文章的范围,因此如果您有兴趣了解更多内容,请参阅

    同样值得注意的是,考虑到这一点,普通程序员不需要自己实现这种宏。但事实上,它们只是被定义为宏本身——因为它们是内置的,所以没有特殊的魔力


    然而,球拍的
    for
    循环看起来不像传统的C-style
    for
    循环,因为C-style
    for
    循环是非常必要的。另一方面,Scheme和Racket倾向于使用功能性风格,这种风格避免了变异,并且看起来更具声明性。因此,Racket的循环试图描述更高级的迭代模式,比如循环一系列数字或循环一个列表,而不是描述值应该如何更新的低级语义。当然,如果您真的想要这样的东西,它几乎与上面定义的
    for loop
    宏相同,尽管有一些细微的区别。

    我想进一步介绍一下Alexis的优秀答案。下面是一个示例用法,演示了她所说的
    do
    与您的
    for循环
    几乎相同的意思:

    (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还为理解提供了
    ,Alexis的回答中也提到了这一点,这也被认为是传统的,下面是它的样子:

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

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

    我想重写Alexis的优秀答案和Chris Jester Young为不熟悉
    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))
    
    #朗球拍
    (定义语法规则(用于循环[var init check change]expr…)
    (本地[(定义(循环变量值))
    (如果检查
    (循环更改(开始表达式…)
    价值]]
    (循环初始化#f)))
    (对于循环[i0(
    因此,
    语法规则
    defmacro
    在相当重要的方面是不同的,而Scheme/Racket与Clojure是非常不同的。如果您愿意,您也可以使用宏在Racket中实现类似的概念,但是它看起来有点不同Clojure和Racket是相关的语言,但是它们不一样,而且它们肯定不兼容源代码。然而,Racket已经附带了一些非常好的for循环,它们本身就是普通的旧宏。有这么多有用的,但不是经典的。我们如何在Racket中为此编写宏。我在回答中指出了如何在Racket中实现此功能,但也值得注意的是,它与您在问题中提到的for loop
    宏几乎相同。然而,我已经在我的回答中阐述了为什么这个构造几乎从未被使用。我总是将
    do
    翻译为
    begin
    。为什么更喜欢让
    ?好奇。@ChrisJester-Young因为
    let
    允许内部定义,而
    begin
    不允许。有时,
    begin
    更可取,因为它是如何拼接到周围的上下文中的(它不会引入新的范围),但在这种情况下,没有上下文可拼接,因此,
    let
    始终是更可取的方法