在一个新的包中,我想要:use
(继承)包packa
和packb
。
packa
和packb
导出符号有交集
怎么可能从packa
继承所有,但只有那些packb
不与packa
相交?
更新:
我已经尝试了:use
packa
和packb
中的:import-from
符号,我需要并且不与packa
发生冲突。然而,这是相当繁琐的。
我已经尝试了do-external-symbols
和intern
/import
,但这似乎不起作用,或者至少我不知道它是如何工作的。
在这种情况下,阴影是避免冲突的方法。
如果您想使用packa中的符号而不带前缀,请在包定义中使用:shadowing-import-from #:packa #:sym1 #:sym2 ...
。或者,如果你喜欢那些没有前缀的符号,可以使用packb。
如果您希望为两个包中所有冲突的符号使用前缀,请使用:shadow #:sym1 #:sym2 ...
。
我假定您所说的'导出符号的交集'是指两个包具有导出符号名称的交集,而不是符号本身(见下文)。因此,例如,我假设包是这样定义的:
(defpackage :p1
(:use)
(:export #:s1 #:s2))
(defpackage :p2
(:use)
(:export #:s2 #:s3))
表示P1
和P2
都输出一个名为"S2"
的符号,但(eq 'p1:s2 'p2:s2)
为假。
在这种情况下,不能同时使用P1
和P2
。您可以通过显式导入(或shadowing-import
ing)符号来修改内容,但通常这是非常不可取的,更不用说混乱了。
defpackage
的扩展变体,可以满足您的需要。
使用这个系统,并使用上面的包定义,你可以这样说来创建一个管道:
(define-conduit-package :p3
(:use)
(:extends :p1)
(:extends/excluding :p2 #:s2))
这个新的包P3
现在重新导出P1
和P2
中的符号,除了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)))))))
现在如果我说(使用上面P1
和P2
的最新定义:
> (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
包重叠的任何符号。