Obviously, the externally visible API is published by exporting symbols. But… what if I have multiple packages (say A, B and C) and A’s exported symbols are not all meant to be part of the external API – some of them are needed for B and C? (similarly, B exports some symbols for A and C and some for the external API; C is the ‘toplevel’ package and all its exported symbols are part of the public API; I want to keep things modular and allow A to hide its innards from B and C, so I avoid ‘::’).
My solution right now is to re-export everything that is meant to be public from C and document that the public API consists only of C’s exported symbols and people should stay away from public symbols of A and B under pain of bugs and code broken in the future when internal interfaces change.
Is there a better way?
UPDATE: This is my implementation of my understanding of Xach’s answer:
First, let me complete my example. I want to export symbols symbol-a-1 and symbol-a-2 from package a, symbols symbol-b-1 and symbol-b-2 from package b and symbols api-symbol-1 and api-symbol-2 from package c. Only the symbols exported from c are part of the public API.
First, the definition for a:
(defpackage #:a
(:use #:cl))
Note that there aren’t any exported symbols 🙂
A helper macro (uses Alexandria):
(defmacro privately-export (package-name &body symbols)
`(eval-when (:compile-toplevel :load-toplevel :execute)
(defun ,(alexandria:format-symbol *package*
"IMPORT-FROM-~a"
(symbol-name package-name)) ()
(list :import-from
,package-name
,@(mapcar (lambda (to-intern)
`',(intern (symbol-name to-intern) package-name))
symbols)))))
Use the macro to ‘export privately’ 🙂 :
(privately-export :a :symbol-a-1 :symbol-a-2)
Now the definition of b:
(defpackage #:b
(:use #:cl)
#.(import-from-a))
… b‘s ‘exports’:
(privately-export :b :symbol-b-1 :symbol-b-2)
… c‘s definition:
(defpackage #:c
(:use #:cl)
#.(import-from-a)
#.(import-from-b)
(:export :api-symbol-1 :api-symbol-2)
Problems with this approach:
acannot use symbols fromb(withoutimporting symbols frombfromaafter both have been defined);- the syntax
package:symbolis basically not usable for symbols exported ‘privately’ (it’s either justsymbolorpackage::symbol).
If A and B are primarily for the implementation of C, you can have C’s defpackage form drive things with selective use of
:import-from, since you can import things that aren’t external. Then you can selectively re-export from there.