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

标签 lisp common-lisp clisp

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

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

例如,假设我们有哈希表:

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

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

(< (d 4) 3)

我们希望返回一个哈希表,表示它有 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

最佳答案

对此有三个答案:

  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:* 绑定(bind)了在 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 中根据某些双参数定义的方式像上面的 +/2 这样的函数是这样的(这在实现上可能是错误的,但数学是这样的):

  • (+ a)a
  • (+ a b)(+/2 a b);
  • (+ a b ...) 是 (+/2 a (+ b ...)` 并且根据关联性这很好(但要注意 float )。

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

  • (+) 是字段的加法标识(同样,(*) 是乘法标识)。

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

一般来说,如果 +* 表示一个字段的两个操作,那么如果允许这些操作符的零参数版本,它们应该返回两个标识该领域的(他们在 CL 中所做的)。但这意味着它们定义的所有领域都必须共享这两个身份(同样,它们在 CL 中这样做:有理数和复数领域共享身份)。这反过来意味着必须允许 (+ x 3)(来自 (+ x (*) (*) (*)))(依此类推)。或者您要么不允许零参数情况,要么它们代表字段操作,或者两者兼而有之。

关于lisp - 在 Common Lisp 中,您将如何扩展现有的比较器操作以适用于新类型?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/72481746/

相关文章:

lisp - 在 LISP 中出现 "bad binding form"错误

lisp - 无法打开模块文件 : net/sendmail

common-lisp - 如何使用 Caveman2 设置响应状态代码?

macros - defun宏在lisp中是如何实现的?

function - 这个 Lisp 函数有什么问题?

lisp - 在 Lisp 代码中使用 (sqrt x)

opengl - 在 FreeBSD 上安装 lisp/opengl

variables - 口齿不清;符号和全局变量

recursion - Lisp 排列函数——我在这里做错了什么?

lisp - Lisp `if` 运算符中的嵌套函数