最近,我一直在思考Lisp的基础;我已经阅读了几篇互联网上的手册和/或其他材料,包括 P. Graham 的 The Roots of Lisp:
在 The Roots of Lisp 中,描述了 quote
是一种将代码转换为数据的原始方法,从而将其引用,但似乎没有等效的反向原始方法,即 unquote
原语。我原以为可能是eval
的业务,但eval
经常在null词法环境中运行数据,这与将数据转换回代码不等价。
因此,为什么没有unquote
的Lisp原语呢?
最近,我一直在思考Lisp的基础;我已经阅读了几篇互联网上的手册和/或其他材料,包括 P. Graham 的 The Roots of Lisp:
在 The Roots of Lisp 中,描述了 quote
是一种将代码转换为数据的原始方法,从而将其引用,但似乎没有等效的反向原始方法,即 unquote
原语。我原以为可能是eval
的业务,但eval
经常在null词法环境中运行数据,这与将数据转换回代码不等价。
因此,为什么没有unquote
的Lisp原语呢?
unquote
只在quasiquote
的上下文中有用,并且quasiquote
可以作为宏来实现(在幕后使用quote
)。因此,没有必要有一个unquote
原语;quasiquote
宏只需处理找到的unquote
符号即可。
(quasiquote
是Scheme中反引号引用的名称。因此:
`(foo bar ,baz)
读入为
(quasiquote (foo bar (unquote baz)))
这里是一个非常简单的Scheme quasiquote
宏(它只处理列表,与标准的quasiquote
不同,后者还可以处理向量和其他数据类型):
(在Scheme中。)
(define-syntax quasiquote
(syntax-rules (unquote unquote-splicing)
((quasiquote (unquote datum))
datum)
((quasiquote ((unquote-splicing datum) . next))
(append datum (quasiquote next)))
((quasiquote (datum . next))
(cons (quasiquote datum) (quasiquote next)))
((quasiquote datum)
(quote datum))))
使用所有标准读者缩写的等效版本:
(define-syntax quasiquote
(syntax-rules (unquote unquote-splicing)
(`,datum
datum)
(`(,@datum . next)
(append datum `next))
(`(datum . next)
(cons `datum `next))
(`datum
'datum)))
quasiquote
,供您参考(尽管我不太了解 CL,无法编写 CL 版本)。 - C. K. Young(foo l)
与宏的工作方式有关,而不是与 unquote
的工作方式有关。如果我们不讨论宏,你想要的情况下 unquote
怎么工作? - C. K. Young我对Lisp也比较新,但是我认为你想要的是eval
。 eval
是将数据转换为代码的方法。
具体地说,考虑一个简单的函数。
(defun foo (a b c) (list a b c))
CL-USER> (foo 'a 'b 'c)
(A B C)
CL-USER> '(foo 'a 'b 'c)
(FOO 'A 'B 'C)
添加一个引号会产生预期的效果:
CL-USER> ''(foo 'a 'b 'c)
'(FOO 'A 'B 'C)
现在让我们使用eval
来解开它,本质上可以将其视为quote
的反向操作。它是反向操作。x轴是数据形式,y轴是代码形式。希望这个(有点牵强)的比喻有意义。
CL-USER> (eval ''(foo 'a 'b 'c))
(FOO 'A 'B 'C)
eval
会发生什么吗?这就是结果:CL-USER> (eval (eval ''(foo 'a 'b 'c)))
(A B C)
Matt Brown和Jens Palsberg在通过内部类型函数进行类型化自我评估中实际上提供了一个很好的unquote定义。在该定义下,eval过程是一个元解释器函数,它将结构化输入转换为具有相同类型的不同值。即,接受Lisp程序作为s表达式,并返回另一个s表达式作为结果。
引用形式应该保持结构,以便如果程序自然运行,然后对结果进行引用,则生成的表示应与引用程序然后运行eval时相同。
举个更具体的例子,假设您将JavaScript程序表示为字符串(为简单起见,假设程序是返回某些JavaScript对象的0参数函数)。自然运行此程序,然后获取JS对象输出(例如可能是循环的),并在输出上运行引用应返回与在程序的字符串表示上运行eval相同的字符串。
function program() {
...
return obj;
}
// If we had a true quote operation in JS, we would be
// able to run const quotedProgram = quote(program);
const quotedProgram = `
function program() {
...
return obj;
}
`;
const result1 = program();
const result2 = quote(result1);
const result3 = eval(quotedProgram);
const result4 = unquote(result3);
上面的例子有点奇怪,因为JS没有自然的方法将任意函数引用为字符串(toString在许多情况下可以工作)。但是请注意,如果引用/评估正确,则result2和result3应该相同;此外,如果取消引用正确,则result1和result4应该相同。
unquote
本身(正如我在我的答案中提到的那样已经提供了),而是一种类似于 JavaScript 的eval
的local-eval
,其中词法变量是可用的。 - C. K. Youngunquote
实际上相当于一个local-eval
,比简单的quote
或quasiquote
系统要复杂得多,因此不适合作为原语实现。 - C. K. Younglocal-eval
?到目前为止我还不清楚。 - SaltyEgg(defun unquote (quoted-sexp) quoted-sexp)
。它利用了 CL 中函数参数在处理之前被求值的特性。 - Alexej Magura