在 Common Lisp 中如何使用特殊的运算符/表单实现"+"



在On Lisp(第9页)中,可以找到以下声明:

函数是 Lisp 程序的构建块。它们也是 Lisp 的构建块。在大多数语言中,+ 运算符是完全不同的东西 从用户定义的函数。但是Lisp有一个单一的模型,函数应用程序,可以 描述程序完成的所有计算。Lisp + 运算符是一个函数, 就像您可以定义自己的那些一样。 事实上,除了少数称为特殊形式的运算符外,核心 Lisp 是 Lisp 函数的集合。是什么阻止你添加到这个 收集?什么都没有:如果你想到一些你希望 Lisp 能做的事情,你 可以自己编写,你的新函数会像内置函数一样对待 的。

我的问题是,使用以下特殊运算符究竟如何实现像+运算符这样的东西?还是实际上使用了更多的运算符,而格雷厄姆只是不精确和戏剧化?

block      let*                  return-from      
catch      load-time-value       setq             
eval-when  locally               symbol-macrolet  
flet       macrolet              tagbody          
function   multiple-value-call   the              
go         multiple-value-prog1  throw            
if         progn                 unwind-protect   
labels     progv                                  
let        quote    

有没有办法查看这些函数的源代码?

他并不是说每个函数都是根据这些特殊形式实现的。

他说+(像其他所有函数一样)是一个函数

  • 可以使用正常的语法(+ x y z)调用它(其中+是函数,xyz参数)。
  • 参数将首先被评估:(f (g) (h))在调用f之前会同时调用gh,即使f恰好是+
  • 你可以像任何其他函数一样传递它,并通过funcallapply调用它:(let ((x #'+)) (funcall x 1 2 3))是6。

关键是这些属性不一定适用于特殊形式。if不会首先评估其所有论点;您不能引用let并间接调用它;等。

当然,这仍然为"编译器魔术"敞开了大门。在某些时候,+必须执行低级操作,例如取决于数字的实现方式。细节看起来会有所不同,具体取决于您的 Lisp 编译器。

cl:+的行为就像一个函数,并且被视为一个函数。

您不必自己定义它,也不必只能使用其他已定义的 CL 标准运算符来定义它。cl:+的定义很可能使用特定于实现的内部功能。

Common Lisp 有三种类型的运算符:函数特殊运算符

该标准没有说明它们是如何实现的。我们有理由相信,Common Lisp 实现将有额外的内部运算符用于实现标准运算符。

  • 有固定数量的特殊运算符 - 尽管 CL 实现可能会提供额外的特殊运算符。特殊运算符不是函数,用户无法定义特殊运算符。

  • CL定义了一堆标准宏和用户定义新宏的方法。未指定如何定义标准宏。

  • CL定义了一堆标准函数和方法,供用户定义新功能。未指定如何定义标准函数。

这是 src/code/numbers.lisp 中 sbcl 的源代码

(macrolet ((define-arith (op init doc)
`(defun ,op (&rest numbers)
(declare (explicit-check))
,doc
(if numbers
(let ((result (the number (fast-&rest-nth 0 numbers))))
(do-rest-arg ((n) numbers 1 result)
(setq result (,op result n))))
,init))))
(define-arith + 0
"Return the sum of its arguments. With no args, returns 0.")
(define-arith * 1
"Return the product of its arguments. With no args, returns 1."))

在实际层面上,您观察到仅使用核心特殊形式来实现+并不是特别有效,而没有任何算术感知的低级实用程序(因为 CPU 往往非常擅长算术)。

我相信,Graham 的观点更多的是,Common Lisp 中的+函数中没有任何固有的东西会阻止你自己定义它。在大多数语言中(C++是另一个例外),+将以与用户定义函数根本不同的方式实现。两个常见原因是+通常使用中缀表示法,而函数调用使用func(arg1, arg2)表示法;而且通常不可能使用加号等随机符号来定义新的用户创建的函数。

另一方面,Common Lisp没有这个限制。+使用与任何其他函数相同的语法结构,因此原因不适用;类似地,Common Lisp 允许您在函数名称中使用许多不同的字符。

例如,这在 GNUclisp中工作正常(带有警告),而在 JavaScript 中不可能做同样的事情:

(defun + (a b) (- a (- 0 b))) ; using '- to avoid having to look up other forms

在其他语言中,Algol 是最常见的,APL 可能是最明显的,使用户定义的抽象与语言附带的功能和形式无法区分是非常特殊的。

因此,我认为您误读了文本。想象一下,你想创建一个数字库,用间隔来扩展数字的语言概念。您可以创建一个库并定义一种创建间隔的方法,并使您的库处理数字和间隔。如果你有一个做数学的库,你可以在库中使用你的类,它会开箱即用地工作,只要你已经定义了你需要的所有函数。现在在大多数Algol语言中+/,...不是函数而是运算符,它们具有特殊的规则,因此即使C++使运算符重载,也没有多少其他语言支持制作适用于新间隔类型的+。解决方案将是显然看起来不像语言自己的原语的函数,因为它们是运算符。在APL中,所有基元都有特殊的符号,并且所有用户定义的符号都没有,因此所有用户定义的抽象都很容易看到,因为它们不是花哨的符号。

今天,我们经常推出Java,JavaScript,C++等的新修订版。这是必需的,因为这些语言没有办法在语言中抽象自己的语法。今天的 JavaScript 有 babel,它基本上给你提供了一种将一种语法转换为另一种语法的方法,但它还没有从语言中转译一个功能,让你做到这一点。这些是Lisp几十年来一直拥有的东西,现代语言正在获得Lisp功能,但有些仍然很少。

我制作了一种Lisp语言,它与第一个Lisp一样缺乏数字,因为我对它不感兴趣。我还必须用这种语言制作99瓶啤酒。我使用列表和符号,并制作和定义了+-zerop。当您查看实际程序时,您看不到它缺少数字。

我喜欢Guy L. Steele在98年关于扩展编程语言的演讲。他是Scheme的原作者,并参与了许多语言,包括Java和Common Lisp。

最新更新