Macros 球拍中的For循环宏
本页提到了在Lisp中实现类似C的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宏系统非常强大,
(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代码有许多变化:
`
和~
(分别发音为“quasikote”和“unquote”)将值“插值”到模板中。语法规则
表单自动执行此替换,因此无需显式执行引号value#
和newvalue#
)来防止名称冲突,但Racket的宏系统是卫生的,因此这种转义是完全不必要的,宏中绑定的标识符默认自动存在于它们自己的范围内循环
和递归
,但Racket支持尾部递归,因此翻译只使用,这对于立即调用的lambda来说是一种非常简单的糖分,它调用自身let
而不是do
,使用省略号而不是&steps
来标记多次出现,使用let
的语法,以及使用#f
而不是nil
来表示没有值for loop
宏时不使用逗号,因为,
在Racket中的含义不同。在Clojure中,它被视为空白,因此在那里它也是完全可选的,但在Racket中,这将是一个语法错误然而,球拍的
for
循环看起来不像传统的C-stylefor
循环,因为C-stylefor
循环是非常必要的。另一方面,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
始终是更可取的方法