我正在尝试实现一种领域特定语言的 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
最佳答案
对此有三个答案:
- 不,你不能这样做,因为
+
&c 不是 CL 中的通用函数,你不能重新定义从CL
包中导出的符号 ...< - ...但是,是的,如果您愿意接受少量的不便,您当然可以这样做,因为 Lisp 是一种可编程的编程语言...
- ...但是不,事实上,如果您想保留扩展语言的语义,您不能完全这样做,而且您对此无能为力.
那么让我们按顺序看一下。
(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/