为什么Lisp原语中没有“unquote”?

16

最近,我一直在思考Lisp的基础;我已经阅读了几篇互联网上的手册和/或其他材料,包括 P. Graham 的 The Roots of Lisp:

The Roots of Lisp 中,描述了 quote 是一种将代码转换为数据的原始方法,从而将其引用,但似乎没有等效的反向原始方法,即 unquote 原语。我原以为可能是eval的业务,但eval 经常在null词法环境中运行数据,这与将数据转换回代码不等价。

因此,为什么没有unquote的Lisp原语呢?


2
重新阅读您的问题,我得出的印象是,您想要的不是 unquote 本身(正如我在我的答案中提到的那样已经提供了),而是一种类似于 JavaScript 的 evallocal-eval,其中词法变量是可用的。 - C. K. Young
1
@ChrisJester-Young 是的,那就是我的意思。 - SaltyEgg
@ChrisJester-Young,我确实想知道为什么这种“引用”不是原语。因为有一个“引用”,为什么作者没有包括它的反转函数呢? - SaltyEgg
1
你看,你想要的那种 unquote 实际上相当于一个 local-eval,比简单的 quotequasiquote 系统要复杂得多,因此不适合作为原语实现。 - C. K. Young
谢谢。还有一个问题:在Scheme或CL中是否有local-eval?到目前为止我还不清楚。 - SaltyEgg
@SaltyEgg 试试:(defun unquote (quoted-sexp) quoted-sexp)。它利用了 CL 中函数参数在处理之前被求值的特性。 - Alexej Magura
3个回答

15
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)))

在 Scheme 中,宏不仅可以在符号上工作,还可以在带有词法信息标记的标识符上工作(赞 hygienic 宏!)。除非您有一个特定的例子,您认为我的示例没有充分解决问题。 - C. K. Young
我不熟悉Scheme,我在Common Lisp中测试了一个案例,但它没有起作用。 - SaltyEgg
1
@SaltyEgg 我已经添加了一个 Scheme 实现的 quasiquote,供您参考(尽管我不太了解 CL,无法编写 CL 版本)。 - C. K. Young
你在运行环境上测试过这个了吗?例如,从(read)获取一个S表达式,然后尝试取消引用?我对Scheme了解很少,所以需要确保一下。 - SaltyEgg
@SaltyEgg 在你的例子中无法使用 (foo l) 与宏的工作方式有关,而不是与 unquote 的工作方式有关。如果我们不讨论宏,你想要的情况下 unquote 怎么工作? - C. K. Young
显示剩余2条评论

3

我对Lisp也比较新,但是我认为你想要的是evaleval 是将数据转换为代码的方法。

具体地说,考虑一个简单的函数。

(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)

0

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应该相同。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接