如何使用 "taken" 词作为 CLOS 泛型

How to use a "taken" word as a CLOS generic

泛型似乎提供了一个很好的工具,可以提取一个常用词并让它根据您传递的类型对事物起作用,并具有事后可扩展性。

但是那些已经被使用但没有被定义为通用的常用词呢?如果我尝试定义 REMOVE,例如:

(defclass reticulator () (splines))

(defmethod remove ((item reticulator)
     sequence &key from-end test test-not start end count key))

我在 SBCL 中遇到错误:

COMMON-LISP:REMOVE already names an ordinary function or a macro.

对于"generic-ify"这些内置函数之一,是否有成语或认可的方式?人们这样做吗?

为了看看会发生什么,我尝试用泛型覆盖 REMOVE:

(defgeneric remove (item sequence &key from-end test test-not start end count key))

WARNING: redefining COMMON-LISP:REMOVE in DEFGENERIC

我不知道是否有 "good" 方法可以做到这一点,它可以通过管道传输到旧的实现,并允许为想要赋予单词新含义的特定类型重载。

为什么会这样?

Common Lisp 的第一个版本于 1981/82 年开始设计,结果于 1984 年作为 Common Lisp the Language 一书出版。该语言本身主要基于在 Lisp Machine Lisp(又名 Zetalisp)上。 Zetalisp 比当时发布的 Common Lisp 大得多,并且包含一个称为 Flavors 的早期对象系统。 Zetalisp 中的大部分内容都是以面向对象的方式实现的——这确实降低了性能,而且在 Lisp 机器上对性能的影响并不大——但他们有专门的处理器,优化了指令集。所以 Common Lisp 不包含任何对象系统,因此它针对当时典型处理器的性能进行了轻微优化。在后来的 Common Lisp 版本中,将添加一个对象系统——当有足够的 Lisp 面向对象扩展经验时——记住,我们谈论的是 80 年代初期。

这个 1984 年的 Common Lisp 具有有限形式的通用行为。例如,函数 REMOVE 适用于序列 - 序列是一种新类型,它具有向量和列表作为子类型。

后来 Common Lisp 从 1986 年开始标准化,有人在寻找 Common Lisp 的对象系统 - none 所提议的已经足够好了 - 因此基于 New Flavors 开发了一个新的系统(来自Symbolics,上述 Flavors 的更新版本)和 Common Loops(来自 Xerox PARC)。那些已经具有通用功能,但单一调度。 CLOS 然后添加了多个分派。

决定不使用 CLOS 泛型函数替换基本函数 - 原因之一是性能:CLOS 泛型函数需要相对复杂的调度机制,而这种调度是在运行时决定的。没有 CLOS 功能可以进行静态编译时分派,也没有标准化功能可以使 类 'sealed' -> 的部分无法更改。因此,像 CLOS 这样的高动态系统会产生运行成本。

一些函数被定义为 CLOS 通用函数(如 PRINT-OBJECT),一些实现有很大一部分 Common Lisp 和 CLOS 实现(流,条件,......) - 但这是特定于实现的,而不是标准要求。还有一些库提供了内置 CL 功能的基于 CLOS 的功能:例如 I/O 具有可扩展的基于 CLOS 的流。

另请注意,重新定义现有的 Common Lisp 函数是未定义的行为。

所以 Common Lisp 选择提供一个强大的对象系统,但将其留给各个实现,他们想在基本语言中使用 CLOS 的程度 - 其局限性在于被标准化为普通非 CLOS 的功能 -用户通常不应将通用函数替换为 CLOS 函数。

一些 Lisp dialects/implementations 试图处理这些问题并试图定义一个更快的 CLOS 变体,这将成为大部分语言的基础。参见例如 Apple 的 Dylan 语言。对于一些较新的方法,请参阅 Julia 语言。

你自己改进的 Common Lisp

包(-> 符号命名空间)允许您定义自己的改进 CL:这里我们定义了一个新包,其中包含所有 CL 符号,只有 cl:remove 被其自己的符号遮蔽。然后我们定义一个名为 bettercl::remove 的 CLOS 通用函数并编写两个示例方法。

CL-USER 165 > (defpackage "BETTERCL" (:use "CL") (:shadow cl:remove))
#<The BETTERCL package, 1/16 internal, 0/16 external>

CL-USER 166 > (in-package "BETTERCL")
#<The BETTERCL package, 1/16 internal, 0/16 external>

BETTERCL 167 > (defgeneric remove (item whatever))
#<STANDARD-GENERIC-FUNCTION REMOVE 4060000C64>

BETTERCL 168 > (defmethod remove (item (v vector)) (cl:remove item v))
#<STANDARD-METHOD REMOVE NIL (T VECTOR) 40200AB12B>

BETTERCL 169 > (remove 'a #(1 2 3 a b c))
#(1 2 3 B C)

BETTERCL 170 > (defmethod remove ((digit integer) (n integer))
                 "remove a digit from an integer, returns a new integer"
                 (let ((s (map 'string
                               (lambda (item)
                                 (character (princ-to-string item)))
                               (cl:remove digit
                                          (map 'vector
                                               #'digit-char-p
                                               (princ-to-string n))))))
                   (if (= (length s) 0) 0 (read-from-string s))))
#<STANDARD-METHOD REMOVE NIL (INTEGER INTEGER) 40200013C3>

BETTERCL 171 > (remove 8 111888111348111)
11111134111

现在您还可以从 BETTERCL 导出符号,这样您就可以在您的应用程序包中使用这个包,而不是包 CL.

这种方法以前用过。例如 CLIM(Common Lisp 接口管理器)定义了一个包 CLIM-LISP,用作编程的方言。

Common Lisp 有时同时提供函数和相关的 CLOS 通用函数

参见标准函数DESCRIBE, which can be extended by writing methods for the standard CLOS generic function DESCRIBE-OBJECT

Common Lisp 的改进实验

另一种使标准 Common Lisp 函数可扩展的方法是将它们替换为使用可扩展的基于 CLOS 的协议的版本。请注意,如何替换标准功能是特定于实现的,效果也可能是特定于实现的。例如,如果编译器已将内置函数内联到代码中,则重新定义将不会影响已内联的代码。

有关此方法的示例,请参阅 Christophe Rhodes User-extensible sequences in Common Lisp 的论文 (PDF)。

什么时候使用泛型函数?

有几点需要注意:

  • 在有多个相关方法时定义CLOS泛型函数,理想情况下受益于通用扩展机制,并且可能会延长

  • 考虑性能命中

  • 不要将单个 CLOS 通用函数用于具有相似 arglist 甚至相同名称但适用于非常不同的域的函数

这意味着您不应在单个 CLOS 泛型函数中定义函数,例如:

; do some astrophysics calculations
(defmethod rotate-around ((s star) (u galaxy)) ...)

; do some computation with graphics objects
(defmethod rotate-around (shape (a axis)) ...)

例如,编写 :before:around:after 方法可能不会产生有用的结果。

一个可以有两个不同的 rotate-around 泛型函数,一个在包 ASTRO-PHYSICS 中,另一个在包 GRAPHICS-OBJECTS 中。因此,这些方法不会在同一个 CLOS 通用函数中,扩展它们可能会更容易。