Par*_*ife 3 lisp macros common-lisp
如果在REPL中我这样做:
(dolist (x (1 2 3))
(print x))
Run Code Online (Sandbox Code Playgroud)
然后我得到一个错误,因为在(1 2 3)中数字1不是符号或lambda expr.如果我做:
(dolist (x (list 1 2 3))
(print x))
Run Code Online (Sandbox Code Playgroud)
然后它工作正常.
我的问题是为什么以下工作:
REPL> (defmacro test (lst)
(dolist (x lst)
(print x)))
=> TEST
REPL> (test (1 2 3))
1
2
3
=>NIL
Run Code Online (Sandbox Code Playgroud)
为什么dolist在宏定义中接受(1 2 3)而在直接在repl中时不接受?假设:
"由于TEST是一个宏,它不会评估它的参数,因此(1 2 3)会按原样传递给dolist宏.因此,玩家必须像在REPL中传递(1 2 3)时那样抱怨"
显然是错的.但是哪里?
更新:虽然答案有助于澄清对宏的一些误解,但我的问题仍然存在,我将尝试解释原因:
我们已经建立了dolist评估其列表参数(代码块1,2).好吧,在宏定义中调用它并且传递给它的list参数是定义的宏参数之一(代码块3)似乎并非如此.更多细节:调用时,宏不会评估其参数.因此,我的测试宏在调用时将保留list参数,并在扩展时将其原样传递给dolist.然后在扩展时,玩家将被执行(在我的测试宏def中没有反引号).并且它将以(1 2 3)作为参数执行,因为这是测试宏调用传递给它的内容.那么为什么它不会抛出错误,因为dolist试图评估它的列表参数,在这种情况下它的列表参数(1 2 3)是不可评估的.我希望这有点清除我的困惑.
这种形式:
(defmacro test (lst)
(dolist (x lst)
(print x)))
Run Code Online (Sandbox Code Playgroud)
定义一个宏,它是一个"代码转换函数",它在宏扩展时使用该宏应用于表单.因此,在定义此宏之后,在计算此表达式时:
(test (1 2 3))
Run Code Online (Sandbox Code Playgroud)
它首先被读到这个列表:
(test (1 2 3))
Run Code Online (Sandbox Code Playgroud)
然后,由于Lisp test在操作员位置读取,因此通过将参数(即文字列表(1
2 3))传递给上面定义的宏扩展函数来进行宏扩展.这意味着在宏扩展时评估以下内容:
(dolist (x '(1 2 3))
(print x))
Run Code Online (Sandbox Code Playgroud)
因此,在宏观扩展时,会打印出三个值.最后,返回该表单的返回值作为要编译和执行的代码. Dolist返回nil这里,所以这是返回的代码:
nil
Run Code Online (Sandbox Code Playgroud)
Nil评估为nil,返回.
通常,这样的宏不是很有用.请参阅Peter Seibel的"Practical Common Lisp"或Paul Graham的"On Lisp",了解有用的宏.
更新:概括阅读,扩展和评估Lisp代码的顺序可能很有用.
首先,REPL接收一个字符流:( t e s t
( 1 2 3 ) )它组装成令牌:( test ( 1 2 3 ) ).
然后,将其转换为符号树:(test(1 2
3)).在该步骤中可能涉及所谓的读取器宏.例如,'x被翻译为(quote x).
然后,从外部进入,检查操作员位置中的每个符号(即,表格中的第一个位置).如果它命名一个宏,则使用代码(即符号子树)调用相应的宏函数,该代码是表单的其余部分作为参数.宏函数应该返回一个新的形式,即代码,它取代了宏形式.在您的情况下,宏test获取code(1 2 3)作为参数,打印其中包含的每个符号(请注意,这甚至在编译时间之前),并返回nil,抛出其参数(编译器甚至从未看到您的小列表).然后再次检查返回的代码以查找可能的宏扩展.
最后,评估,即编译和执行不再包含任何宏调用的扩展代码. Nil恰好是一个自我评价的象征; 它评估为nil.
这只是一个粗略的草图,但我希望它清除一些东西.
| 归档时间: |
|
| 查看次数: |
595 次 |
| 最近记录: |