Import Common Lisp:CLO和包/如何导入和合并泛型

Import Common Lisp:CLO和包/如何导入和合并泛型,import,common-lisp,packages,clos,Import,Common Lisp,Packages,Clos,假设我们有两个包,每个包定义一个类,并为具有相同名称的插槽/泛型方法导出符号 (defpackage pkg1 (:export _class1 _slot _reader _method)) (in-package pkg1) (defclass _class1 () ((_slot :initform "SLOT111" :initarg :slot :reader _reader))) (defmethod _method ((self _class1)) (format t "SLOT-

假设我们有两个包,每个包定义一个类,并为具有相同名称的插槽/泛型方法导出符号

(defpackage pkg1 (:export _class1 _slot _reader _method))
(in-package pkg1)
(defclass _class1 () ((_slot :initform "SLOT111" :initarg :slot :reader _reader)))
(defmethod _method ((self _class1)) (format t "SLOT-: ~a~%" (_reader self)))

(defpackage pkg2 (:export _class2 _slot _reader _method))
(in-package pkg2)
(defclass _class2 () ((_slot :initform "SLOT222" :initarg :slot :reader _reader)))
(defmethod _method ((self _class2)) (format t "SLOT=: ~a~%" (_reader self)))
我们如何在第三个包中导入这些符号,成功地合并(而不是隐藏)泛型

(defpackage test)
(in-package test)
... ; here we somehow import symbols _slot, _reader and _method
    ; from both packages, so they get merged (like in 'GNU Guile' or 'Gauche')
(defvar v1 (make-instance '_class1))
(defvar v2 (make-instance '_class2))
(_reader v1) (_method v1) ; both must work
(_reader v2) (_method v2) ; and these too

说到CLOS,我真的是个笨蛋,所以去年我做了同样的实验。我的发现是CL并没有真正导出方法或合并方法。它导出可能具有绑定的符号。因此,您需要使用他们应该共享的符号制作一个包,并可能将文档放在其中:

;;通用符号与文档
(defpackage接口(:导出_插槽_读取器_方法))
(包内接口)
(非通用方法(自)
(:文档“此操作实现此功能”))
(非通用读取器(自身)
(:文档“这就是功能”))
(defpackage pkg1(:use:cl:interface)(:export\u class1\u slot\u reader\u method))
(包装pkg1中)
(defclass _class1()(_slot:initform“SLOT111”:initarg:slot:reader _reader)))
(defmethod _method((self _class1))(格式t“SLOT-:~a~%”(_readerself)))
(defpackage pkg2(:use:cl:interface)(:导出_class2 _slot _reader _method))
(包装pkg2中)
(defclass _class2()((_slot:initform“SLOT222”:initarg:slot:reader _reader)))
(defmethod _method((self _class2))(格式t“SLOT=:~a~%”(_readerself)))
(除包装测试(:使用:cl:pkg1:pkg2))
(包装内测试)
(defvar v1(生成实例'\u class1))
(defvar v2(生成实例'\u class2))
(_读取器v1);==>“SLOT111”
(_方法v1);==>无(输出“插槽-:插槽111”)
(_阅读器v2);==>“SLOT222”
(_方法v2);==>无(输出“插槽-:插槽222”)
您可以从测试中查看发生了什么:

(describe '_method) 

_METHOD is the symbol _METHOD, lies in #<PACKAGE INTERFACE>, is accessible in 
4 packages INTERFACE, PKG1, PKG2, TEST, names a function.
Documentation as a FUNCTION:
This does this functionality

 #<PACKAGE INTERFACE> is the package named INTERFACE.
 It imports the external symbols of 1 package COMMON-LISP and 
 exports 3 symbols to 2 packages PKG2, PKG1.

 #<STANDARD-GENERIC-FUNCTION _METHOD> is a generic function.
 Argument list: (INTERFACE::SELF)
 Methods:
 (_CLASS2)
 (_CLASS1)

(describe '_reader) 

_READER is the symbol _READER, lies in #<PACKAGE INTERFACE>, is accessible in 
 4 packages INTERFACE, PKG1, PKG2, TEST, names a function.
Documentation as a FUNCTION:
This does that functionality

 #<PACKAGE INTERFACE> is the package named INTERFACE.
 It imports the external symbols of 1 package COMMON-LISP and 
 exports 3 symbols to 2 packages PKG2, PKG1.

 #<STANDARD-GENERIC-FUNCTION _READER> is a generic function.
 Argument list: (INTERFACE::SELF)
 Methods:
 (_CLASS2)
 (_CLASS1)
(描述'\u方法)
_方法是符号_方法,位于#,可在中访问
4个包接口,PKG1,PKG2,TEST,命名一个函数。
文件作为一项功能:
这就实现了这一功能
#是名为INTERFACE的包。
它导入1个软件包COMMON-LISP和
将3个符号导出到2个程序包PKG2、PKG1。
#是一个泛型函数。
参数列表:(接口::SELF)
方法:
(_CLASS2)
(_类1)
(描述“读者”)
_读卡器是一种符号_READER,位于#,可在中访问
4个包接口,PKG1,PKG2,TEST,命名一个函数。
文件作为一项功能:
这就实现了该功能
#是名为INTERFACE的包。
它导入1个软件包COMMON-LISP和
将3个符号导出到2个程序包PKG2、PKG1。
#是一个泛型函数。
参数列表:(接口::SELF)
方法:
(_CLASS2)
(_类1)
这样做的副作用是,如果您从使用
pkg2
的包中获取实例,那么导入
pkg1
方法将对
pkg2
实例起作用


现在这个房间里有一头大象。为什么不在
接口
中定义一个基类,并将其添加为
\u class1
\u class2
的父类呢。您只需进行一些更改即可轻松完成此任务,但这并不是您所要求的。

在尝试通过MOP解决此任务后,我想出了一个更简单的解决方法:

(defmacro wrapping-import
          (sym-name &rest sym-list)
  `(defmethod ,sym-name
              (&rest args)
     (loop for sym in '(,@sym-list) do
           (let ((gf (symbol-function sym)))
             (if (compute-applicable-methods gf args)
               (return (apply gf args)))))
     (error "No applicable method found in ~A" ',sym-name)))
例如:

(defpackage p1 (:export say-type))
(in-package p1)
(defmethod say-type ((v integer)) "int")

(defpackage p2 (:export say-type))
(in-package p2)
(defmethod say-type ((v string)) "str")

(in-package cl-user)
(wrapping-import say-type p1:say-type p2:say-type)

(say-type "") ; -> "str"
(say-type 1) ; -> "int"
此外,以下是原始解决方案:

(defmacro merging-import
          (sym-name &rest sym-list)
  (let ((gf-args (clos:generic-function-lambda-list
                  (symbol-function (first sym-list)))))
    `(progn
       (defgeneric ,sym-name ,gf-args)
       (loop for sym in '(,@sym-list) do
             (loop for meth
                   in (clos:generic-function-methods (symbol-function sym))
                   do
                   (add-method #',sym-name
                               (make-instance 'clos:standard-method
                                              :lambda-list  (clos:method-lambda-list  meth)
                                              :specializers (clos:method-specializers meth)
                                              :function     (clos:method-function     meth)))))))) 
请注意,
wrapping import
即使在泛型函数的签名不匹配的情况下也能工作,而
合并import
要求它们的lambda列表相等。
现在我想知道:为什么我们要在2017年发明这样的东西?为什么这些还不在标准中

以防有人需要它——一个宏,它的工作原理类似于Python中pkg import*的

(defmacro use-all-from
          (&rest pkg-list)
  `(loop for pkg-name in '(,@pkg-list) do
         (do-external-symbols
          (sym (find-package pkg-name))
          (shadowing-import (read-from-string (format nil "~a:~a"
                                                      pkg-name sym))))))

如果方法在概念上对于不同的类是相同的操作,那么像这样定义一个协议(/接口)是有意义的(作为一种启发式,如果一个docstring可以有效地描述这两种方法)。如果这些方法是完全不同的操作,那么最好只使用包限定名,或者在方法名前面加上合适的(不同的)协议名。谢谢,但即使这个解决方案有效,从设计角度来看,它也是完全错误的。如果两个包_pkg1和_pkg2都是由不同的开发人员编写和维护的呢?那么为了工作双方应该相互了解吗?“如果一类和二类完全不相关,为什么它们必须有共同的祖先呢?”阿列克斯达克沃德我同意。如果我要设计一个lisp OOP系统,导入将合并具有相同导入名称的调度程序,如果它们的签名兼容,并且连接将影响它们从中导入的库,以便两者都支持它们导入的支持库的所有类型。除非您使用编译语言,否则它会变得很混乱。我认为可以通过编写自定义宏来解决此问题,该宏在当前包中定义泛型,并使用
add method
将所需包中的方法填充到该宏中,如下所示:
(在包测试中)(合并import\u method\u pkg1:\u method\u pkg2:\u method)(合并导入_reader _pkg1:_reader _pkg2:_reader)
。但仍然存在一个问题:如何从泛型函数获取方法列表?