对于Lisp初学者来说,最不容易理解的就是Lisp强大的宏。在宏定义中你看到的最多的字符可能是就是引用和反引用。引用和反引用构成了Lisp的基石,所以我先就引用和反引用做一个介绍。宏就留在以后介绍了。
引用Quote其实是Lisp 25个特殊操作符之一,它接受一个单一表达式作为其参数,并且简单的返回它。不经过求值过程。例如下面表达式(quote (+ 2 3)) 求值的话得到是列表 (+ 2 3),而不是5. Quote是读取宏(reader macro)的一个例子。 读取宏可以修改读取器用来将文本转化为Lisp对象的语法。
Quote 的等价形式是‘ ,‘是Quote的语法糖。两个形式是完全等价了。读取器将’翻译为quote的等价形式。
例如:(defunmess-with
(number string)
'(value-of-number (1+ number) something-with-string (length string)))
运行之后结果如下:
Lisp> (mess-with 20 "foo")
(VALUE-OF-NUMBER (1+ NUMBER) SOMETHING-WITH-STRING (LENGTH STRING))
但是我们实际的意思是想要 number的值和 String的长度组成的一个列表。上面的写法是不能实现我们的要求的。看下面的这个定义:
(defun mess-with (number string)
(list 'value-of-number (1+ number) 'something-with-string (length string)))
执行结果如下:
Lisp> (mess-with 20 "foo")
(VALUE-OF-NUMBER 21 SOMETHING-WITH-STRING 3)
反引用和引用是等价的,下面是上面函数定义的反引用版本:
(defun mess-with (number string )
`(value-of-string ,(+ 1 number ) something-with-string ,(length string )) )
下面是另一个例子:
Lisp> (quote spiffy-symbol)
SPIFFY-SYMBOL
Lisp> 'spiffy-symbol ; '是quote的等价形式,和上一个表达式相同
SPIFFY-SYMBOL
如果我们不使用引用,那么结果如下:
Unbound variable: SPIFFY-SYMBOL
[Condition of type UNBOUND-VARIABLE]
Restarts:
0: [CONTINUE] Retry getting the value of SPIFFY-SYMBOL.
Unbound variable: SPIFFY-SYMBOL
[Condition of type UNBOUND-VARIABLE]
Restarts:
0: [CONTINUE] Retry getting the value of SPIFFY-SYMBOL.
因为没有spiffy-symbol。
反引用(backquote)是引用(quote)的特别版本,它可以用来创建Lisp表达式的模板。反引用最常见的用途之一是用在宏定义里。
反引用字符“‘”得名的原因是:它和通常的引号“’”相似,只不过方向相反。当单独把反引用作为表达式前
缀的时候,它的行为和引号一样:
‘(a b c) 等价于’(a b c).
只有在反引用和逗号“,”,以及comma-at
“,@”一同出现时才变得有用。如果说反引用创建了一个模板,那
么逗号就在反引用中创建了一个slot。一个反引用列表等价于将其元素引用起来,调用一次list。也就
是,
‘(a b c) 等价于(list ’a ’b ’c).
在反引用的作用域里,逗号要求Lisp:“把引用关掉”。当逗号出现在列表元素前面时,它的效果就相当于取
消引用,让Lisp把那个元素按原样放在那里。所以
‘(a ,b c ,d) 等价于(list ’a b ’c d).
插入到结果列表里的不再是符号b,取而代之的是它的值。无论逗号在嵌套列表里的层次有多深,它都仍
然有效,
> (setq a 1 b 2 c 3)
3>
‘(a ,b c)
(A 2 C)
> ‘(a (,b c))
(A (2 C))
而且它们也可以出现在引用的列表里,或者引用的子列表里:
> ‘(a b ,c (’,(+ a b c)) (+ a b) ’c ’((,a ’b)))
(A B 3 (’6) (+ A B) ’C ’((1 ’B)))
一个逗号能抵消一个反引用的效果,所以逗号在数量上必须和反引用匹配。如果某个操作符出现在逗号的
外层,或者出现在包含逗号的那个表达式的外层,那么我们说该操作符包围了这个逗号。例如在‘(,a ,(b
‘,c)) 中,最后一个逗号就被前一个逗号和两个反引号所包围。通行的规则是:一个被n个逗号包围的逗
号必须被至少n+ 1个反引号所包围。很明显,由此可知:逗号不能出现在反引用的表达式的外面。只要
遵守上述规则,就可以嵌套使用反引用和逗号。下面的任何一个表达式如果输入到toplevel下都将造成错
误:
,x ‘(a ,,b c) ‘(a ,(b ,c) d) ‘(,,‘a)
嵌套的反引用是Lisp宏的难点,正如《On Lisp》所说的:
“为了定义一个定义宏的宏,我们通常会要用到嵌套的反引用。嵌套反引用的难以理解是出了名的。尽管最
终我们会对那些常见的情况了如指掌,但你不能指望随便挑一个反引用表达式,都能看一眼,就能立即说出
它可以产生什么。这不能归罪于Lisp。就像一个复杂的积分,没人能看一眼就得出积分的结果,但是我们
不能因为这个就把问题归咎于积分的表示方法。道理是一样的。难点在于问题本身,而非表示问题的方法。”
stackoverflow有一个问答对Lisp的嵌套反引用解释的很透彻,我们下面来分析一下:
先来说一下嵌套反引用的解析规则:
CLTS2里面说:
如果反引号是嵌套的话,最内层的反引用形式(这是说的是最内层的逗号,其实是最外层的反引号)应该最先被展开。这意味着如果某个表达式有几个连续的逗号的话,最左边的那个逗号属于最里面的反引号。
R5RS Scheme语言规范关于反引号这么定义:
准引用可以嵌套。
举例分析:
第一次求值得到如下表达式:
解析:1,左边反引用首先被展开(第一个反引用),所以(+ 1 2)被求值因为匹配逗号(第二个逗号)。
2,另一个表达式,(+ 3 4)因为没有足够的逗号所以未求值。
3,只有一个反引用被展开,因为反引用不会递归的展开。
第二次求值(展开所有的逗号)
为了在展开所有的反引用,我们采用如下表达式:
(eval ``(a ,,(+ 1 2) ,(+ 3 4)))
所用的反引用被展开,我们带到下面的求值结果:
(A 3 7)
解析: 其实就是对第一次求值的结果继续求值,就可以得到上面的结果。
<<On Lisp>>一书上说,反引用的嵌套一般发生在定义宏的宏上面。下面是书中的一个例子:定义一个宏的简称的的宏。
因为一些CL的名字相当的长,比如destructuring-bind 和multiple-value-bind,所以我们可以定义宏来减少我们输入的字符
(defmacro dbind (&rest args)
`(destructruing-bind ,@args))
和
(defmacro mvbind (&rest args)
`(multiple-value-bind ,@args))
就可以了。我们可以看到dbind和mvbind是何等的相似。对于Lisp来说,宏是抽象和消除重复的好方法,那么我们为什么不再定义一个宏来
消除重复呢?假设我们想要得到一个abbrev宏,它允许我们使用(abbrev mvbind mutiple-value-bind)来定义缩写mvbind。下面是这个宏的定义:
(defmacro abbrev (short long)
`(defmacro ,short (&rest args)
`(,',long ,@args)))
卧槽,(,',XXXX),到这一步,我相信初学Lisper肯定凌乱了。其实我何尝不是呢。下面让我们一步一步分析这个宏定义是怎么来的。
我们可以从它的展开式开始,我们最终要一个如下的展开式:
(defmacro mvbind (&rest args)
`(multiple-value-bind ,@args))
我们如果先把multiple-value-bind从反引用中拉出来的话,推到就容易一点,得到如下等价的定义
(defmacro mvbind (&rest args)
(let ( (name 'multiple-value-bind ))
`(,name ,@args) ) )
现在我们将这个展开式转化为一个模板。我们把反引用放到前面,然后将可变的表达式变为一个变量
`(defmacro ,short (& rest args)
(let (( name ',long ))
`(,name ,@args) ) )
最后一步,我们把name 从内层反引用中消除,得到abbrev的宏的主体:
`(defmacro ,short (&rest args)
`(,',long ,@args) ) )
下面我们来正向分析,来展开abbrev宏,例如(abbrev mvbind mutiple-value-bind)
第一步:
首先展开最内层的反引用,和第一个逗号,得到结果
`(DEFMACRO ,SHORT (&REST ARGS) (LIST* ',LONG ARGS))
英文:
Backquote
Thebackquoteintroduces
a template of a data structure to be built. For example, writing
`(cond ((numberp ,x) ,@y) (t (print ,x) ,@y))
is roughly equivalent to writing
(list 'cond
(cons (list 'numberp x) y)
(list* 't (list 'print x) y))
Where a comma occurs in the template, theexpressionfollowing the comma
is to be evaluated to produce anobjectto be inserted at that point. Assumebhas
the value 3, for example, then evaluating theformdenoted by`(a b ,b ,(+ b 1) b)produces
the result(a b 3 4 b).
If a comma is immediately followed by anat-sign, then theformfollowing
theat-signis evaluated to produce alistofobjects.
Theseobjectsare then ``spliced'' into place in the template. For example, ifxhas
the value(a b c), then
`(x ,x ,@x foo ,(cadr x) bar ,(cdr x) baz ,@(cdr x))
=> (x (a b c) a b c foo b bar (b c) baz b c)
The backquote syntax can be summarized formally as follows.
*`basicis the same as'basic, that is,(quotebasic), for anyexpressionbasicthat
is not alistor a generalvector.
*`,formis the same asform, for anyform, provided that the representation offormdoes not begin withat-signordot.
(A similar caveat holds for all occurrences of a form after acomma.)
*`,@formhas undefined consequences.
*`(x1 x2 x3 ... xn . atom)may be interpreted to mean
(append [ x1] [ x2] [ x3] ... [ xn] (quote atom))
where the brackets are used to indicate a transformation of anxjas follows:
--[form]is interpreted as(list `form), which contains a backquoted form that must then be further interpreted.
--[,form]is interpreted as(listform).
--[,@form]is interpreted asform.
*`(x1 x2 x3 ... xn)may be interpreted to mean the same as the backquoted form`(x1 x2 x3 ... xn .nil),
thereby reducing it to the previous case.
*`(x1 x2 x3 ... xn . ,form)may be interpreted to mean
(append [ x1] [ x2] [ x3] ... [ xn] form)
where the brackets indicate a transformation of anxjas described above.
*`(x1 x2 x3 ... xn . ,@form)has undefined consequences.
*`#(x1 x2 x3 ... xn)may be interpreted to mean(apply #'vector `(x1 x2 x3 ... xn)).
Anywhere ``,@'' may be used, the syntax ``,.'' may be used instead to indicate that it is permissible to operatedestructivelyon thelist
structureproduced by the form following the ``,.'' (in effect, to usenconcinstead
ofappend).
If the backquote syntax is nested, the innermost backquoted form should be expanded first. This means that if several commas occur in a row, the leftmost one belongs to the innermostbackquote.
Animplementationis free to interpret a backquotedformF1
as anyformF2 that, when evaluated, will produce a result that is thesameunderequalas
the result implied by the above definition, provided that the side-effect behavior of the substituteformF2 is also consistent
with the description given above. The constructed copy of the template might or might not shareliststructure with the
template itself. As an example, the above definition implies that
`((,a b) ,c ,@d)
will be interpreted as if it were
(append (list (append (list a) (list 'b) 'nil)) (list c) d 'nil)
but it could also be legitimately interpreted to mean any of the following:
(append (list (append (list a) (list 'b))) (list c) d)
(append (list (append (list a) '(b))) (list c) d)
(list* (cons a '(b)) c d)
(list* (cons a (list 'b)) c d)
(append (list (cons a '(b))) (list c) d)
(list* (cons a '(b)) c (copy-list d))
Nested Backquote
This is what the Common Lisp HyperSpec says about nestedbackticks:
If the backquote syntax is nested, the innermost backquoted form should be expanded first. This means that if several commas occur in a row, the leftmost one belongs to the innermost backquote.
The R5RS Scheme spec also includes these details aboutbackticks:
Quasiquote forms may be nested. Substitutions are made only for unquoted components appearing at the same nesting level as the outermost backquote. The nesting level increases by one inside each successive quasiquotation, and
decreases by one inside each unquotation.
Also keep in mind that only one backtick gets collapsed per evaluation, just like a regular quote, it's not recursive.
Rules in action
To see how these three details interact, let's expand your example a bit. This expression...
``(a ,,(+ 1 2) ,(+ 3 4))
Gets evaluated to this (in SBCL notation):
`(A ,3 ,(+ 3 4))
- The left backtick got collapsed, so it the
(+ 1 2)
got escaped by the matching comma (the 2ndcomma, according to the HyperSpec).
- On the other hand, the
(+ 3 4)
didn't have enough commas to get expanded (which is what R5RS mentions).
- Only one backtick got collapsed, because backticks don't get recursively expanded.
Expanding both commas
To get rid of the other backtick, another level of evaluation is needed:
(eval ``(a ,,(+ 1 2) ,(+ 3 4)))
Both backticks are gone, and we're left with a plain list:
(A 3 7)
参考:
http://stackoverflow.com/questions/7549550/using-two-backquotes-and-commas-common-lisp
进一步阅读:
Nested Backquotes considered harmful
相关推荐
AutoLISP是一种基于LISP语言的编程环境,专为Autodesk的AutoCAD软件设计,用于扩展AutoCAD的功能和自定义工作流程。这个压缩包文件包含了关于AutoLISP编程的教程,对于想要掌握这一技能的用户来说是宝贵的资源。 在...
3. **数据分析**:Lisp 提供了丰富的数据结构和算法,可以帮助用户更高效地进行数据分析工作。 4. **自动化任务**:通过 Lisp 与 Excel 的集成,可以编写脚本来自动化 Excel 中的数据处理过程,提高工作效率。 ####...
CAD Lisp是一种基于Lisp语言的编程工具,常用于AutoCAD软件中,用于扩展和自定义CAD的功能。在“CAD-lisp.rar”这个压缩包中,包含的是一系列CAD Lisp源代码,这些源代码主要用于实现CAD中的图层管理、对象修改以及...
LISP和Prolog各有优势,LISP强调函数式编程,适合处理动态变化的数据和算法,而Prolog则侧重于逻辑推理,适合处理知识表示和推理。在实际的人工智能项目中,开发者可能会根据任务需求选择合适的语言或结合两者。例如...
AutoLISP和Visual LISP是AutoCAD软件中的两种编程语言,用于扩展AutoCAD的功能和定制化用户界面。这个“AutoLisp全面的AutoLISP和Visual_LISP教程PPT课件.rar”压缩包包含了深入学习这两种语言的资源,对理解和掌握...
AutoLISP是一种基于LISP语言的编程方言,专为Autodesk的AutoCAD软件设计,用于扩展和自动化CAD操作。此压缩包文件“AutoLISP函数参考(明经翻译版).rar”提供了一份全面的AutoLISP函数参考资料,由明经翻译,方便用户...
1. **AutoCAD 的集成开发环境(IDE)**:AutoCAD 自带了名为 Visual LISP 的 IDE,它包含一个编辑器、调试器和自动完成功能,方便编写和测试 AutoLISP 程序。 2. **加载和执行**:AutoLISP 程序通常保存为 `.lsp` ...
第4篇(第22~35章)为visuallisp程序设计应用与整合,主要介绍visual lisp的基本环境、重要函数和程序调试;还介绍了autocad activex对象控制以及visual lisp在e2d、3d、文字编辑、图层管理、块与属性管理、options...
CAD lisp 亩和平方米的互换CAD lisp 亩和平方米的互换CAD lisp 亩和平方米的互换CAD lisp 亩和平方米的互换CAD lisp 亩和平方米的互换
AutoCAD 2010 AutoLISP参考手册是专为AutoCAD 2010设计者和开发者编写的,旨在帮助他们深入理解和利用AutoLISP语言进行程序开发。AutoLISP是一种基于LISP(列表处理)语言的编程环境,特别为AutoCAD定制,允许用户...
《Land of Lisp》和《Machine Learning in Action》是两本非常重要的IT图书,分别涵盖了Lisp编程语言和机器学习这两个核心领域。 首先,让我们深入探讨《Land of Lisp》。这本书由Conrad Barski撰写,旨在将读者...
数据和指令都以列表的形式表示,这种特性使得LISP在处理复杂数据结构时具有优势。 2. **S-表达式**:S-表达式是LISP的基本元素,它可以是原子(如数字、字符串、符号)或列表。列表可以被视为函数调用,第一个元素是...
《Lisp源码200例》是一部集大成之作,涵盖了Lisp编程语言的各种应用场景和技术要点。Lisp,全称“List Processing”,是一种历史悠久且功能强大的编程语言,以其独特的括号语法和高度可扩展性著称。在这个压缩包中,...
AutoLisp是一种基于Lisp语言的编程环境,专为Autodesk的AutoCAD软件设计,用于扩展和自动化CAD操作。这个入门教程将引导你逐步了解AutoLisp的基本概念、语法和功能,帮助你提升AutoCAD的使用效率。 首先,我们要...
通过逐个分析并实践这些源码,学习者可以逐步掌握LISP的语法、编程思想和设计模式,提升编程技能,理解函数式编程的魅力,并可能发展出对LISP独特编程哲学的深刻洞察。这个压缩包中的800个例子无疑为初学者提供了一...
这是LISP和Common LISP编程的上一页,我们正在处理中,将那里的所有书籍都转换为新页面。 请再次检查此页面!!!
7. **特殊形式**:Lisp有一些特殊形式,如` progn`用于执行一系列表达式,`quote`用于防止表达式的求值,以及`let`和`let*`用于局部变量绑定。 8. **迭代和循环**:Lisp提供多种循环结构,如`dotimes`用于基于次数...
全面的AutoLISP和Visual_LISP教程
此资源是 ANSI Common Lisp 的中文翻译版,涵盖了 Common Lisp 语言的基础知识和高级主题。该资源包含了 17 章节,从基础的列表、特殊数据结构、控制流程、函数、输入与输出、符号、数字、宏、Common Lisp 对象系统...