如何通过排除一些来导入外部包符号



在一个新的包中,我想要:use(继承)包packapackb

packapackb导出符号有交集

怎么可能从packa继承所有,但只有那些packb不与packa相交?

更新:

我已经尝试了:usepackapackb中的:import-from符号,我需要并且不与packa发生冲突。然而,这是相当繁琐的。

我已经尝试了do-external-symbolsintern/import,但这似乎不起作用,或者至少我不知道它是如何工作的。

在这种情况下,阴影是避免冲突的方法。

如果您想使用packa中的符号而不带前缀,请在包定义中使用:shadowing-import-from #:packa #:sym1 #:sym2 ...。或者,如果你喜欢那些没有前缀的符号,可以使用packb。

如果您希望为两个包中所有冲突的符号使用前缀,请使用:shadow #:sym1 #:sym2 ...

我假定您所说的'导出符号的交集'是指两个包具有导出符号名称的交集,而不是符号本身(见下文)。因此,例如,我假设包是这样定义的:

(defpackage :p1
(:use)
(:export #:s1 #:s2))
(defpackage :p2
(:use)
(:export #:s2 #:s3))

表示P1P2都输出一个名为"S2"的符号,但(eq 'p1:s2 'p2:s2)为假。

在这种情况下,不能同时使用P1P2。您可以通过显式导入(或shadowing-importing)符号来修改内容,但通常这是非常不可取的,更不用说混乱了。

在这种情况下,一个好的方法是定义一个管道包,这个包只是作为您和一个或多个实现包之间的管道,根据需要重新导出符号。最简单的方法是使用预先封装的方式来创建导管包,例如Tim Bradshaw的"导管包"系统,可以在Quicklisp中使用。(我想ASDF的一些分支机构也有类似的系统,但我不熟悉那个系统。)它提供了defpackage的扩展变体,可以满足您的需要。

使用这个系统,并使用上面的包定义,你可以这样说来创建一个管道:

(define-conduit-package :p3
(:use)
(:extends :p1)
(:extends/excluding :p2 #:s2))

这个新的包P3现在重新导出P1P2中的符号,除了P2:S2:

(use-package :p3)
t
> (symbol-package 's1)
#<The P1 package, 0/16 internal, 2/16 external>
> (symbol-package 's2)
#<The P1 package, 0/16 internal, 2/16 external>
> (symbol-package 's3)
#<The P2 package, 0/16 internal, 2/16 external>

显然你可以提供多个符号名来排除,你可以选择:假设你现在有

(defpackage :p1
(:use)
(:export #:s1 #:s2 #:s3)
(defpackage :p2
(:use)
(:export #:s2 #:s3 #:s4))
(define-conduit-package :p3
(:use)
(:extends/excluding :p1 #:s3)
(:extends/excluding :p2 #:s2))

然后,这一次没有使用P3,以便更容易看到:

> '(p3:s1 p3:s2 p3:s3 p3:s4)
(p1:s1 p1:s2 p2:s3 p2:s4)

还可以定义只扩展包的导管,包括某些符号名,例如:

(define-conduit-package :p4
(:use)
(:extends/including :p1 #:s1)
(:extends/excluding :p2 #:s1))

将告诉它只从P1中重新导出名为"S1"的符号,而不从P2中重新导出任何具有此名称的符号。

最后你当然可以用函数定义导管。例如:

(defun make-conduit-package-excluding (n for-package/s excluding-from-package/s)
;; extend FOR-PACKAGE/S, excluding exports from EXCLUDING-FROM-PACKAGE/S
(let ((conduit (make-package n :use '()))
(excluders (if (listp excluding-from-package/s)
(mapcar #'find-package excluding-from-package/s)
(list (find-package excluding-from-package/s)))))
(dolist (p (if (listp for-package/s)
(mapcar #'find-package for-package/s)
(list (find-package for-package/s)))
conduit)
(do-external-symbols (s p)
(let ((sn (symbol-name s)))
(unless (some (lambda (excluder)
(multiple-value-bind (ss status) (find-symbol sn excluder)
(and (eq status ':external)
(not (eq ss s)))))
excluders)
(import s conduit)
(export s conduit)))))))

现在如果我说(使用上面P1P2的最新定义:

> (make-conduit-package-excluding "P5" "P1" "P2")
#<The P5 package, 0/16 internal, 1/16 external>
> (use-package "P5")
t
> (use-package "P2")
t

一切又正常了,因为我告诉函数P5不应该从P1重新导出任何符号,这将与P2的导出相冲突。


关于"导出符号的交集"的注释。如果您有两个包导出一些相同的符号,而不是具有相同名称的不同符号,那么同时使用它们是没有问题的。例如:

(defpackage :cl-re-1
(:use :cl)
(:export #:defpackage))

(defpackage :cl-re-2
(:use :cl)
(:export #:defpackage))

然后

> 'cl-re-1:defpackage
defpackage
> 'cl-re-2:defpackage
defpackage
> (use-package :cl-re-1)
t
> (use-package :cl-re-2)
t

如果问题是因为您不想手动写下所有重叠的函数——你可以让一个函数帮你写下来。

比如在

的设置中
(defpackage :p1
(:use :cl)
(:export #:s1 #:s2 #:s3))
(in-package :p1)
(defconstant s1 1)
(defconstant s2 2)
(defconstant s3 3)
(in-package :cl-user)
(defpackage :p2
(:use :cl)
(:export #:s2 #:s3 #:s4))
(in-package :p2)
(defconstant s2 "b")
(defconstant s3 "c")
(defconstant s4 "d")
(in-package :cl-user)
(defpackage :p3
(:use :cl :p1 :p2)
(:shadowing-import-from :p1 #:s2 #:s3)
(:export #:s1 #:s2 #:s3 #:4))
(in-package :p3)
;; package local nicknames
;; https://gist.github.com/phoe/2b63f33a2a4727a437403eceb7a6b4a3
(in-package :cl-user)
(defpackage :p4
(:use #:cl #:p1 #:p2)
(:shadowing-import-from #:p2 #:s2 #:s3)
(:export #:s1 #:s2 #:s3 #:s4))
(defpackage #:p5
(:use #:cl-user)
(:export #:s2 #:s3 #:s4))

我更喜欢使用未被干扰的关键字("#:"),而不是"污染"关键字包的内容

然后可以定义辅助函数:
(defun make-uninterned-keyword (name)
"String to Uninterned Keyword"
(read-from-string (format nil "#:~a" name)))
(defun make-keyword (name)
"String to Keyword"
(values (intern (string-upcase name) "KEYWORD")))
(defun get-symbols (package)
"Return all symbols of a package 
(`package` should be a string)"
(let (symbols)
(do-external-symbols (s (find-package (string-upcase package)))
(push s symbols))
(nreverse (mapcar #'symbol-name symbols))))
;; (lambda (s) (read-from-string (format nil "#:~a" (symbol-name s))))
(defun %overlapping-symbols (package-1 package-2)
"Determine symbols overlapping between the packages"
(let ((symbols-1 (get-symbols package-1))
(symbols-2 (get-symbols package-2)))
(intersection symbols-1 symbols-2 :test #'string=)))
(defun overlapping-symbols (package &rest packages)
"Determine symbols overlapping from first package with the rest of the packages"
(remove-duplicates (loop for pkg in packages
nconcing (%overlapping-symbols package pkg))
:test #'string=))
(defun generate-shadowing-import-form (package &rest packages)
"Construct code for shadowing-import-from"
(let* ((overlapping-symbols (apply #'overlapping-symbols package packages))
(overlapping-keywords (mapcar #'make-uninterned-keyword overlapping-symbols)))
`(:shadowing-import-from ,(make-uninterned-keyword package) ,@overlapping-keywords)))
(defun shadowing-import-string (package &rest packages)
"Construct string for :shadowing-import-from directive"
(string-downcase (format nil "~s" (apply #'generate-shadowing-import-form package packages))))

所以通过运行:

(shadowing-import-string "P2" "P1" "P5")
;; => "(:shadowing-import-from #:p2 #:s2 #:s3 #:s4)"

你得到你想复制粘贴到你的DEFPACKAGE定义。然后列出:p2包中与:p1和/或:p5包重叠的任何符号。

最新更新