Macros 宏应该有副作用吗?
宏扩展是否会(或应该)产生副作用?例如,下面是一个宏,它实际上在编译时获取网页的内容:Macros 宏应该有副作用吗?,macros,racket,side-effects,Macros,Racket,Side Effects,宏扩展是否会(或应该)产生副作用?例如,下面是一个宏,它实际上在编译时获取网页的内容: #lang racket (require (for-syntax net/url)) (require (for-syntax racket/port)) (define-syntax foo (lambda (syntx) (datum->syntax #'lex (port->string (ge
#lang racket
(require (for-syntax net/url))
(require (for-syntax racket/port))
(define-syntax foo
(lambda (syntx)
(datum->syntax #'lex
(port->string
(get-pure-port
(string->url
(car (cdr (syntax->datum syntx)))))))))
然后,我可以做(foo“http://www.pointlesssites.com/“
,它将被替换为”\r\n简短答案
宏有副作用是可以的,但您应该确保程序在提前编译时不会改变行为
较长的答案
带有副作用的宏是一个功能强大的工具,它可以让你做一些让程序更容易编写的事情,或者让你做一些根本不可能做到的事情。但是当你在宏中使用副作用时,有一些陷阱需要注意。幸运的是,Racket提供了所有的工具来确保你能正确地做到这一点
最简单的宏副作用是使用一些外部状态来查找要生成的代码。问题中列出的示例(阅读Google API说明)就是这种类型。更简单的示例是include
宏:
#lang racket
(include "my-file.rktl")
这将读取myfile.rktl
的内容,并将其放置在使用include
表单的地方
现在,include
不是构造程序的好方法,但这在宏中是一种相当良性的副作用。如果您提前编译文件,它的工作原理是一样的,因为include
的结果是文件的一部分
另一个不好的简单例子如下:
#lang racket
(define-syntax (show-file stx)
(printf "using file ~a\n" (syntax-source stx))
#'(void))
(show-file)
这是因为printf
仅在编译时执行,因此如果您提前编译使用show file
的程序(与raco make
一样),那么printf
将发生,而在程序运行时不会发生,这可能不是您的本意
幸运的是,Racket有一种技术可以让您有效地编写宏,如show file
。基本思想是保留实际执行副作用的剩余代码。特别是,您可以使用Racket的begin for syntax
表单来实现此目的。下面是我编写show file
的方法:
#lang racket
(define-syntax (show-file stx)
#`(begin-for-syntax
(printf "using file ~a\n" #,(syntax-source stx))))
(show-file)
现在,在展开show file
宏时,printf
发生在show file
生成的代码中,源代码嵌入展开的语法中。这样,您的程序就可以在提前编译的情况下正常工作
宏的其他用途也有副作用。Racket中最突出的一个用途是模块间通信——因为require
不会产生所需模块可以获得的值,所以在模块间通信最有效的方法是使用副作用。要使这项工作在编译的存在下重新进行对于语法
,要求几乎完全相同的技巧
这是一个Racket社区,特别是我,思考了很多的话题,并且有几篇学术论文讨论了这是如何工作的:
,Matthew Flatt,ICFP 2002
,Ryan Culpeper,Sam Tobin Hochstadt和Matthew Flatt,2007年计划研讨会
,Sam Tobin Hochstadt,Ryan Culpeper,Vincent St Amour,Matthew Flatt和Matthias Felleisen,PLDI 2011在公共Lisp中,函数eval when允许您决定宏何时展开。好的,derp。我刚刚意识到我链接的文档发现库正在编译时读取文件,这是另一种形式的side效果。这并不一定意味着这是一件好事,但它是在真实的图书馆中完成的,所以……如果你认为这是一个“副作用”,我最喜欢你的记忆结果的例子。(与读取本地或远程文件的示例相反。在编译时读取JSON发现文档与读取.RKT文件没有太大区别。事实上,我几乎朝着这个方向读取了发现文档,只是我希望它们按原样工作,而不是需要添加#lang行。但事实上,它可以像这样工作--JSON发现文档可以是一种“语言”——这对于Racket来说是一件非常酷的事情。)我喜欢这样。问一个涉及到库的问题,库的作者会随意给你留下一条评论:)我仍在学习Racket的所有语言扩展/构建功能,但我学的越多,我就越惊讶。你能再解释一下语法的
开始吗?我看了文档[但我没有真正理解它。Racket指南中有一个更容易理解的讨论:(还有后面的部分)。好的,我想我明白了。谢谢!