宏扩展可以(或应该)有副作用吗?例如,这是一个实际上在编译时抓取网页内容的宏:
#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<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\"\r\n\t <and so on>"
这是好习惯吗?我是不是觉得Racket只运行一次这段代码?如果我在宏中添加(display "running...")
行,它只打印一次,但我不想从一个例子中推广...
另外,举一个具有不同副作用的宏的例子:我曾经构建了一个宏,它将一小部分Racket翻译成(eta-expanded)lambda演算(当然,它仍然可以在球拍)。每当宏完成一个函数的翻译时,它会将结果存储在一个字典中,以便以后调用宏可以在自己的翻译中使用该函数定义。
答案 0 :(得分:13)
宏可以有副作用,但是你应该确保你的程序在提前编译时不会改变行为。
带副作用的宏是一个功能强大的工具,它可以让你做一些使程序更容易编写或者启用根本不可能的东西的东西。但是当您在宏中使用副作用时,需要注意一些陷阱。幸运的是,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
不会产生需求模块可以获得的值,在模块之间进行通信最有效的方法是使用副作用。要在编译的情况下使这项工作需要与begin-for-syntax
几乎完全相同的技巧。
这是Racket社区的一个主题,特别是我对此有很多想法,并且有几篇学术论文在讨论它是如何工作的:
Composable and Compilable Macros: You want it when?,Matthew Flatt,ICFP 2002
Advanced Macrology and the Implementation of Typed Scheme,Ryan Culpepper,Sam Tobin-Hochstadt和Matthew Flatt,2007年计划研讨会
Languages as Libraries,Sam Tobin-Hochstadt,Ryan Culpepper,Vincent St-Amour,Matthew Flatt和Matthias Felleisen,PLDI 2011
答案 1 :(得分:1)
在常见的Lisp中,函数eval-when允许您决定何时扩展宏。