最近,我一直在思考 Lisp 的基础;我在互联网上阅读了几本手册和/或其他材料,包括P. Graham的 The Roots of Lisp:
在Lisp 的根源中,quote
被描述为将代码更改为数据的原语,从而引用它,但似乎没有等效的逆原语,即unquote
原语。我以为可能是eval
的事,但eval
经常在空词法环境中运行数据,这并不等同于将数据改回代码。
因此,为什么没有unquote
Lisp 原语?
最近,我一直在思考 Lisp 的基础;我在互联网上阅读了几本手册和/或其他材料,包括P. Graham的 The Roots of Lisp:
在Lisp 的根源中,quote
被描述为将代码更改为数据的原语,从而引用它,但似乎没有等效的逆原语,即unquote
原语。我以为可能是eval
的事,但eval
经常在空词法环境中运行数据,这并不等同于将数据改回代码。
因此,为什么没有unquote
Lisp 原语?
unquote
仅在 的上下文中有用quasiquote
,并且quasiquote
可以实现为宏(quote
在幕后使用)。所以没有必要有一个unquote
原始的;quasiquote
宏只处理被发现的符号unquote
。
(quasiquote
是反引号引用的方案名称。因此:
`(foo bar ,baz)
读入为
(quasiquote (foo bar (unquote baz)))
在方案中。)
这是一个非常简单的 Schemequasiquote
宏(它只处理列表,与标准不同quasiquote
,它也处理向量和其他数据类型):
(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)))
我对 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
连续连接两个 s 会发生什么吗?这里是:
CL-USER> (eval (eval ''(foo 'a 'b 'c)))
(A B C)
Matt Brown 和 Jens Palsberg 实际上通过内涵类型函数为类型化自我评估中的 unquote 提供了一个很好的定义。该定义下的 eval 过程是一个元解释器函数,它将结构化输入转换为具有相同类型的不同值。即,接受一个 Lisp 程序作为 s 表达式并返回另一个 s 表达式作为结果。
引用形式应该是结构保留的,这样如果程序自然运行然后引用结果,则结果表示应该与引用程序然后运行 eval 相同。
对于更具体的示例,假设您将 JavaScript 中的 JavaScript 程序表示为字符串(为简单起见,假设程序是返回某个 JavaScript 对象的 0 参数函数)。自然地运行这个程序,然后获取 JS 对象输出(例如可能是循环的)并在输出上运行 quote 应该返回与在程序的字符串表示上运行 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 类型的作品)。但是,请注意,如果quote/eval 正确,则result2 和result3 应该相同;此外,如果 unquote 正确,则 result1 和 result4 应该相同。