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

Common Lisp: CLOS and packages / how to import and merge generics

假设我们有两个包,每个包定义一个 class 并为 slots/generic 方法导出符号 相同的名称.

(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 并没有真正导出方法或合并方法。它导出可能具有绑定的符号。因此你需要用他们应该共享的符号制作一个包,也许把文档放在那里:

;; common symbols and documantation
(defpackage interface (:export _slot _reader _method))
(in-package interface)
(defgeneric _method (self)
  (:documentation "This does this functionality"))
(defgeneric _reader (self)
  (:documentation "This does that functionality"))

(defpackage pkg1 (:use :cl :interface) (: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 (:use :cl :interface) (: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 (:use :cl :pkg1 :pkg2))
(in-package test)
(defvar v1 (make-instance '_class1))
(defvar v2 (make-instance '_class2))
(_reader v1) ; ==> "SLOT111"
(_method v1) ; ==> nil (outputs "SLOT-: SLOT111")
(_reader v2) ; ==> "SLOT222"
(_method v2) ; ==> nil (outputs "SLOT-: SLOT222")

您可以从测试中查看发生了什么:

(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)

如果您从使用 pkg2 的包中获取这样的实例,那么导入 pkg1 _method 将在 pkg2 个实例上起作用的副作用。

现在这个房间里有一头大象。为什么不在 interface 中定义基数 class 并将其添加为 _class1_class2 的父 class。只需进行一些更改即可轻松做到这一点,但这不是您所要求的。

在尝试通过 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 也能工作,而merging-import 需要 他们的 lambda 列表相等。
现在我想知道:为什么我们必须在 2017 年发明这样的东西?为什么那些还不在标准中?

为了以防万一有人需要它 - 一个宏,其工作方式类似于 Python 中的 from 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))))))