Racket 中的 For 循环宏
For-loop macro in 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#))))
但它给出 "bad syntax" 错误。
您在问题中包含的代码片段是用 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 代码有许多变化:
Clojure 示例使用 `
和 ~
(分别发音为“quasiquote”和“unquote”)将值“插入”到模板中。 syntax-rules
形式自动执行此替换,因此无需显式执行引用。
Clojure 示例使用以散列结尾的名称(value#
和 new-value#
)以防止名称冲突,但 Racket 的宏系统卫生,所以这种转义是完全没有必要的——默认情况下,绑定在宏中的标识符自动存在于它们自己的范围内。
Clojure代码用了loop
和recur
,但是Racket支持尾递归,所以翻译过来就用“named let
”,真的只是一些极其简单的用于调用自身的立即调用的 lambda 的糖。
还有一些其他的语法差异,例如使用 let
而不是 do
,使用省略号而不是 & steps
来标记多次出现, let
的语法,以及使用 #f
而不是 nil
来表示值的缺失。
最后,在for-loop
宏的实际使用中没有使用逗号,因为,
在Racket中表示不同的东西。在 Clojure 中,它被视为空格,因此它在那里也完全是可选的,但在 Racket 中,这将是一个语法错误。
完整的宏教程远远超出了单个 Stack Overflow post 的范围,所以如果您有兴趣了解更多信息,请查看 the Macros section of the Racket guide。
同样值得注意的是,考虑到 Racket already provides a set of very robust for
loops and comprehensions built into the language,普通程序员不需要自己实现这种宏。但事实上,它们只是被定义为宏本身——因为它们是内置的,所以没有什么特别的魔法。
Racket 的 for
循环看起来不像传统的 C 风格 for
循环,但是,因为 C 风格的 for
循环是非常必要的。另一方面,Scheme 和 Racket 倾向于使用 functional 风格,它避免了突变并且通常看起来更具声明性。因此,Racket 的循环试图描述更高级别的迭代模式,例如循环遍历一系列数字或遍历列表,而不是描述应如何更新值的低级语义。当然,如果你真的想要这样的东西,Racket provides the do
loop,它几乎与上面定义的for-loop
宏相同,尽管有一些细微差别。
我想稍微扩展一下 Alexis 的出色回答。这是一个示例用法,演示了她所说的 do
与您的 for-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))
(除了这实际上 return i
的最终值)。
我想为不熟悉 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))
此页面中提到了用于在 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#))))
但它给出 "bad syntax" 错误。
您在问题中包含的代码片段是用 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 代码有许多变化:
Clojure 示例使用
`
和~
(分别发音为“quasiquote”和“unquote”)将值“插入”到模板中。syntax-rules
形式自动执行此替换,因此无需显式执行引用。Clojure 示例使用以散列结尾的名称(
value#
和new-value#
)以防止名称冲突,但 Racket 的宏系统卫生,所以这种转义是完全没有必要的——默认情况下,绑定在宏中的标识符自动存在于它们自己的范围内。Clojure代码用了
loop
和recur
,但是Racket支持尾递归,所以翻译过来就用“namedlet
”,真的只是一些极其简单的用于调用自身的立即调用的 lambda 的糖。还有一些其他的语法差异,例如使用
let
而不是do
,使用省略号而不是& steps
来标记多次出现,let
的语法,以及使用#f
而不是nil
来表示值的缺失。最后,在
for-loop
宏的实际使用中没有使用逗号,因为,
在Racket中表示不同的东西。在 Clojure 中,它被视为空格,因此它在那里也完全是可选的,但在 Racket 中,这将是一个语法错误。
完整的宏教程远远超出了单个 Stack Overflow post 的范围,所以如果您有兴趣了解更多信息,请查看 the Macros section of the Racket guide。
同样值得注意的是,考虑到 Racket already provides a set of very robust for
loops and comprehensions built into the language,普通程序员不需要自己实现这种宏。但事实上,它们只是被定义为宏本身——因为它们是内置的,所以没有什么特别的魔法。
Racket 的 for
循环看起来不像传统的 C 风格 for
循环,但是,因为 C 风格的 for
循环是非常必要的。另一方面,Scheme 和 Racket 倾向于使用 functional 风格,它避免了突变并且通常看起来更具声明性。因此,Racket 的循环试图描述更高级别的迭代模式,例如循环遍历一系列数字或遍历列表,而不是描述应如何更新值的低级语义。当然,如果你真的想要这样的东西,Racket provides the do
loop,它几乎与上面定义的for-loop
宏相同,尽管有一些细微差别。
我想稍微扩展一下 Alexis 的出色回答。这是一个示例用法,演示了她所说的 do
与您的 for-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))
(除了这实际上 return i
的最终值)。
我想为不熟悉 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))