在 Common Lisp 中,您将如何扩展现有的比较器操作以适用于新类型?

In Common Lisp, how would you extend the existing comparator operations to work for new types?

我正在尝试实现一种领域特定语言的 lisp 版本,该语言在离散概率分布上使用二元运算符和比较器。是否有一种安全(大概)和简洁的方法来扩展这些原子的功能 < <= = >= > + - / * 以使用新类型,而不会完全阻塞语言?

我意识到只创建新的运算符名称是最简单的解决方案,但这个答案既不引人注目也不有趣,而且肯定不符合 lisp 作为可编程编程语言的声誉。

例如,假设我们有哈希-table:

((4 . 1/4) (3 . 1/4) (2 . 1/4) (1 . 1/4))

我们用宏(d 4)创建的,我们希望能够将它与这样的数字进行比较

(< (d 4) 3)

我们想要 return 一个散列-table 表示它有 50% 的概率是真的,50% 的概率是假的,以这种形式说:

((0 . 1/2) (1 . 1/2))

但目前它产生错误: *** - <: #S(HASH-TABLE :TEST FASTHASH-EQL (4 . 1/4) (3 . 1/4) (2 . 1/4) (1 . 1/4)) is not a real number

这个有3个答案:

  1. 不,你不能这样做,因为 + &c 不是 CL 中的通用函数,你不能重新定义从 CL 包导出的符号 ...
  2. ...但是,是的,如果您愿意接受一些不便,当然可以这样做,因为 Lisp 是一种可编程的编程语言...
  3. ...但是不,事实上你不能完全如果你想保留你的扩展语言的语义,实际上没有任何东西你可以做到这一点。

所以让我们按顺序看这些。

(1): 算术运算符不是 CL 中的泛型函数 ...

这意味着您无法扩展它们:它们处理数字。而且你不能重新定义它们,因为 the language forbids that.

(2): ...但是你当然可以解决这个问题...

因此,第一个技巧是我们要定义一个包,其中的算术运算符 而不是 它们的 CL 版本。我们可以手动执行此操作,但我们将使用 Tim Bradshaw's conduits system 让生活更轻松:

(in-package :org.tfeb.clc-user)

(defpackage :cl/generic-arith
  ;; The package people will use
  (:use)
  (:extends/excluding :cl
   #:< #:<= #:= #:= #:> #:+ #:- #:/ #:*)
  (:export
   #:< #:<= #:= #:= #:> #:+ #:- #:/ #:*))

(defpackage :cl/generic-arith/user
  ;; User package
  (:use :cl/generic-arith))

(defpackage :cl/generic-arith/impl
  ;; Implementation package
  (:use :cl/generic-arith))

所以现在:

> (in-package :cl/generic-arith/user)
#<The CL/GENERIC-ARITH/USER package, 0/16 internal, 0/16 external>

> (describe '*)

* is a symbol
name          "*"
value         #<unbound value>
function      #<unbound function>
plist         nil
package       #<The CL/GENERIC-ARITH package, 0/1024 internal, 978/1024 external>

所以现在我们可以继续定义这些东西了。有一些问题:因为我们所做的是创建一个环境,例如 * 不是 cl:*,然后任何使用 * 作为符号的东西 将不起作用。因此,例如 cl:* 绑定了在 repl 中评估的最后一个形式的值,并且键入 * 不会获得该符号:您需要键入 cl:*

同样,+ method combination 将不起作用:您必须明确说明

(defgeneric foo (...)
  ...
  (:method-combination cl:+)
  ...)

所以有点痛。但可能不会那么痛。

现在我们可以开始实施了。显而易见的方法是定义泛型函数,它们是我们需要的函数的 2-arg 版本,然后在它们之上定义函数。这意味着我们可以为实际的运算符使用诸如编译器宏之类的东西,而不用担心破坏底层的通用函数。

所以从

开始
(in-package :cl/generic-arith/impl)

(defgeneric +/2 (a b)
  (:method ((a number) (b number))
   (cl:+ a b)))

...

现在你想一想,意识到......

(3) ...但是有些问题你不能解决,用任何语言

问题至少是这样的:+ 是字段上的两个运算符之一,它在 CL 中的定义方式类似于 two-argument 函数+/2 上面是这样的(这在实现上可能是错误的,但数学会这样):

  • (+ a)a;
  • (+ a b)(+/2 a b);
  • (+ a b ...) 是 (+/2 a (+ b ...)` 并且通过关联性很好(但要注意浮点数)。

这看起来不错,但我们错过了一个案例:

  • (+) 是字段的加法恒等式(同样地,(*) 是乘法恒等式)。

哦,等等:什么字段?对于 cl:+ 这很好:它是数字字段的标识。但现在?谁知道?并且对此无能为力:如果您希望 + 像在 CL 中那样工作,但在其他领域除外,您不能那样做。

您可以通过使用 generic-cl 来做到这一点,而且它是可扩展的。

(= "hello" y)  ;; would throw an error in standard CL

你会 :use :generic-cl 而不是包定义中的 :cl,你会在 REPL 中使用 :generic-cl-user

子类方法 equalplessp.