4

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:

  • a cannot use symbols from b (without importing symbols from b from a after both have been defined);
  • the syntax package:symbol is basically not usable for symbols exported 'privately' (it's either just symbol or package::symbol).
Miron Brezuleanu
  • 2,989
  • 2
  • 22
  • 33

3 Answers3

2

You could add a third package, D, that exports all public API symbols, and consider the A, B and C packages private. You could then qualify all definitions of functions and variables in the API package using qualified names like in

(defun D:blah () ...)

to make it easy to visually spot the definitions of public entry points.

hans23
  • 1,034
  • 7
  • 13
  • That's basically what I do now, my C is your D. The suggestion to qualify the definitions of public symbols is food for thought. Thanks! – Miron Brezuleanu Oct 19 '12 at 12:33
2

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.

Xach
  • 11,774
  • 37
  • 38
  • I missed the fact that `:import-from` can be used with non external symbols. However, updating my code to use `:import-from` instead of `:use` means I'll have to duplicate the `:export` list for the imported package in every 'importer' package. Am I missing something? Any suggestions about transforming `:use`d packages into `:import-from` packages with the lists of symbols for `:import-from` factored out? Thanks! – Miron Brezuleanu Oct 19 '12 at 12:39
  • I updated the question with an implementation of my understanding of your answer, I'd love to hear about improvements or corrections. Thanks again! – Miron Brezuleanu Oct 22 '12 at 14:28
1

Probably, the easiest way is proposed by Hans.

You may also wan to take a look at Tim Bradshaw's Conduit packages

Community
  • 1
  • 1
Vsevolod Dyomkin
  • 9,343
  • 2
  • 31
  • 36