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指南中有一个更容易理解的讨论:(还有后面的部分)。好的,我想我明白了。谢谢!