I assume that by 'an intersection of exported symbols' what you mean is that the two packages have an intersection of exported symbol names, not the symbols themselves (see below). So, for instance, I assume the packages are defined something like this:
(defpackage :p1
(:use)
(:export #:s1 #:s2))
(defpackage :p2
(:use)
(:export #:s2 #:s3))
Which means that both P1
and P2
export a symbol named "S2"
but (eq 'p1:s2 'p2:s2)
is false.
In this case you can't use both P1
and P2
. You can massage things by explicitly importing (or shadowing-import
ing) symbols but generally that's quite undesirable, not to mention messy.
A good approach in this case is to define a conduit package, which is a package which simply acts as a conduit between you and one or more implementation packages, reexporting symbols as needed. The simplest approach is to use a pre-canned way of creating conduit packages such as Tim Bradshaw's 'conduit-packages' system, available in Quicklisp. (Some tentacle of ASDF also has a similar system I think but I am not familiar with that one.) This provides an extended variant of defpackage
which does what you need.
Using this system, and with the above package definitions, you might say this to create a conduit:
(define-conduit-package :p3
(:use)
(:extends :p1)
(:extends/excluding :p2 #:s2))
This new package P3
now reexports the symbols from P1
and P2
, except for 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>
You can obviously provide more than one symbol name to exclude, and you can pick and choose: assume you now have
(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))
Then, this time without using P3
to make it easier to see:
> '(p3:s1 p3:s2 p3:s3 p3:s4)
(p1:s1 p1:s2 p2:s3 p2:s4)
You can also define conduits which extend packages only including certain symbol names, for instance:
(define-conduit-package :p4
(:use)
(:extends/including :p1 #:s1)
(:extends/excluding :p2 #:s1))
will tell it to reexport only the symbol named "S1"
from P1
, and not to reexport any symbol with this name from P2
.
Finally you can of course define conduits with functions. For instance:
(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)))))))
Now if I say (using the most recent definitions of P1
and P2
above:
> (make-conduit-package-excluding "P5" "P1" "P2")
#<The P5 package, 0/16 internal, 1/16 external>
> (use-package "P5")
t
> (use-package "P2")
t
Everything is again OK because I told the function that P5
should not reexport any symbols from P1
which would clash with P2
's exports.
A note on 'intersection of exported symbols'. If you have two packages which export some of the same symbols rather than different symbols with the same names, then there is no issue using both. For example:
(defpackage :cl-re-1
(:use :cl)
(:export #:defpackage))
(defpackage :cl-re-2
(:use :cl)
(:export #:defpackage))
Then
> 'cl-re-1:defpackage
defpackage
> 'cl-re-2:defpackage
defpackage
> (use-package :cl-re-1)
t
> (use-package :cl-re-2)
t