重载+运算符以操作Common Lisp向量

4

我希望能够重载+运算符,使其适用于Common Lisp向量——就像线性代数中的向量一样。是否可以使用+运算符进行重载?

这是我的预期定义:

 (defmethod + ((v1 vector) (v2 vector))

感谢您对所有帮助的支持!
4个回答

8

如果我要做这个,我会首先在一个单独的包中进行。然后我会编写一个使用二进制运算符的通用函数:

(defun + (&rest addends)
  (reduce #'binary+ (cdr addends) :initial-value (car addends)))

(defgeneric binary+ (addend1 addend2))

那么您可以在通用函数 binary+ 上定义方法,使您能够将两个向量相加、一个向量和一个标量相加...

以下是一个适合生成包装器的宏:

(defmacro define-operator (op &key (binary-signifier :binary) (package *package*)
  "Defines a generic operator OP, being essentially a reduce operation using
   a generic function whose name is a concatenation of BINARY-SIGNIFIER and OP."
  (let ((op op)
        (binary (intern (concatenate 'string
                                          (string  binary-signifier)
                                          (string op))
                        package)))
    `(progn
       (defun ,op (&rest args)
          (reduce (function ,binary) (cdr args) :initial-value (car args)))
       (defgeneric ,binary (arg1 arg2)))))

然后,您可以像 Joshua Taylor 的回答中那样定义方法:
(defmethod binary+ ((x number) (y number))
  (cl:+ x y))

(defmethod binary+ ((x vector) (y vector))
  (map 'vector 'cl:+ x y))

(defmethod binary+ ((x list) (y list))
  (map 'list 'cl:+ x y))

“auto-generate the wrapping defun” 是什么意思?我不是一个有经验的 Lisp 程序员。 - CodeKingPlusPlus
到了最后,我希望能够写出 (+1 1) = 2 和 (+#(1 2) #(0 -1)) = #(1 1)。 - CodeKingPlusPlus
@CodeKingPlusPlus,你能告诉我为什么要这样做吗?在任何语言中,都应该尽可能地以最惯用的方式工作。虽然在其他语言中运算符重载是可以的,在Common Lisp中泛型函数在某些情况下也是可以的,但是让+成为泛型在Common Lisp中是没有意义的。你可以很容易地使用map来解决你的问题,就像我展示的那样,或者如果你真的需要一个函数来处理数字和向量,那么就创建一个新的函数。 - Mark Karpov
1
@CodeKingPlusPlus Vatine的回答将允许您“编写(+ 1 1)等于2和(+ #(1 2) #(0 -1))等于#(1 1)”。 - Joshua Taylor
@CodeKingPlusPlus 我添加了一个答案,扩展了这种方法并展示了更详细的工作原理。 - Joshua Taylor
1
@Vatine 我将那个答案标记为社区维基,因为它只是在你已经拥有的代码上添加了几行代码。如果你想将其合并到你的答案中,随意! - Joshua Taylor

5

这是对Vatine的答案的扩展,但是加入了更多细节以使实现更加清晰:

(defpackage #:generic-arithmetic 
  (:use "COMMON-LISP")
  (:shadow "+"))

(in-package #:generic-arithmetic)

(defun + (&rest addends)
  (reduce 'binary+ (cdr addends) :initial-value (car addends)))

(defgeneric binary+ (addend1 addend2))

(defmethod binary+ ((x number) (y number))
  (cl:+ x y))

(defmethod binary+ ((x vector) (y vector))
  (map 'vector 'cl:+ x y))

(defmethod binary+ ((x list) (y list))
  (map 'list 'cl:+ x y))

(+ 1 1)
;=> 2

(+ #(1 2) #(0 -1))
;=> #(1 1)

(+ '(1 3) '(3 1))
;=> (4 4)

谢谢。我已经编辑了我的答案,并添加了一个生成包装器的宏。 - Vatine

4

如果首先屏蔽它,就可以重新定义+:

? (shadow '+)

? (defgeneric + (a &rest b))

? (defmethod + ((a number) &rest b) (apply 'cl:+ a b))
? (+ 1 2)
3
? (+ 2 3 4)
9

? (defmethod + ((a string) &rest b) (apply #'cl:concatenate 'string a b))
? (+ "Hello" "World")
"HelloWorld"
? (+ "Hello" " cruel " "World")
"Hello cruel World"

? (defmethod + ((a vector) &rest b) (apply #'map 'vector 'cl:+ a b))
? (let ((v0 #(1 2 3)) (v1 #(4 5 6))) (+ v0 v1))
#(5 7 9)

2
你不需要为apply函数添加引号:(apply '+ 1 2 '(3 4)) - Rainer Joswig
4
尽管这对于一个黑客来说还不错,但对于生产环境来说就不太好了。你的方法最明显的后果是函数 + 的效率变慢了(而我猜想 + 是非常常用的函数)。 - Mark Karpov
1
@RainerJoswig 谢谢,已更改。 - uselpa

3

定义通用函数+可能不是一个好主意,因为这个符号是被锁定的。CLOS与其他语言(如C++)中的对象系统不同,因此术语“重载”可能不完全正确。

实际上,您不需要特殊的函数来求和向量,可以使用map

CL-USER> (let ((v0 #(1 2 3))
               (v1 #(4 5 6)))
           (map 'vector #'+ v0 v1))
#(5 7 9)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接