在 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个答案:
- 不,你不能这样做,因为
+
&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:*
绑定了在 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
。
子类方法 equalp
和 lessp
.
我正在尝试实现一种领域特定语言的 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个答案:
- 不,你不能这样做,因为
+
&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:*
绑定了在 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
。
子类方法 equalp
和 lessp
.