15

Is there a short way of exporting all the symbols from a package or is it the only way to do it in defpackage. I generally write my code in a file foo.lisp which generally starts with (in-package :foo) and put the package definition to a file package.lisp which generally involves something like this:

(in-package :cl-user)

(defpackage :foo
  (:use :cl)
  (:documentation "Bla bla bla."
  (:export :*global-var-1*
           :*global-var-2*
           :function-1
           :function-2
           :struct
           :struct-accessor-fun-1
           :struct-accessor-fun-2
           :struct-accessor-fun-3
           :struct-accessor-fun-4))

My question is: Designing simply an interface using some global variables and functions may not be adequate sometimes, and you have to export some structures. When this is the case, if you don't simply export accessor functions of this struct, you can not manipulate objects of these struct. So, is there an easy way for accomplishing this effect without manually exporting all these accessor functions?

oakenshield1
  • 851
  • 1
  • 11
  • 26

5 Answers5

15

Once the package is created, and all symbols in it created, e.g., by loading your code that implements the package, you can export any symbols you like, e.g., to export all:

(do-all-symbols (sym (find-package :foo)) (export sym))

You'll probably be happier with

(let ((pack (find-package :foo)))
  (do-all-symbols (sym pack) (when (eql (symbol-package sym) pack) (export sym))))

which won't try re-exporting everything from used packages.

Doug Currie
  • 40,708
  • 1
  • 95
  • 119
  • Of course, one could reach the conclusion, that macros like `defstruct` lack a feature. Namely, a switch which enables automatic exporting of the accessors etc. they create. – BitTickler Oct 05 '20 at 00:32
  • It also seems to export local variables from within functions. Can this be fixed? And would it not belong into some `(eval-when (...) ...)` block? – BitTickler Dec 05 '21 at 13:18
4

Evaluating the macroexpanded code, I get an error for the last nil in the defclass form if no class option is supplied and additional errors as the symbols of the export function have to be quoted. Here is a corrected version which seems to work on my common lisp system (sbcl):

(defmacro def-exporting-class (name (&rest superclasses) (&rest slot-specs)
                               &optional class-option)
  (let ((exports (mapcan (lambda (spec)
                           (when (getf (cdr spec) :export)
                             (let ((name (or (getf (cdr spec) :accessor)
                                             (getf (cdr spec) :reader)
                                             (getf (cdr spec) :writer))))
                               (when name (list name)))))
                         slot-specs)))
    `(progn
       (defclass ,name (,@superclasses)
         ,(append 
           (mapcar (lambda (spec)
                     (let ((export-pos (position :export spec)))
                       (if export-pos
                       (append (subseq spec 0 export-pos)
                           (subseq spec (+ 2 export-pos)))
                       spec)))
               slot-specs)
           (when class-option (list class-option))))
       ,@(mapcar (lambda (name) `(export ',name))
                 exports))))


(macroexpand-1
 '(def-exporting-class test1 nil
   ((test-1 :accessor test-1 :export t)
    (test-2 :initform 1 :reader test-2 :export t)
    (test-3 :export t))))

(PROGN
 (DEFCLASS TEST1 NIL
           ((TEST-1 :ACCESSOR TEST-1) (TEST-2 :INITFORM 1 :READER TEST-2)
            (TEST-3)))
 (EXPORT 'TEST-1)
 (EXPORT 'TEST-2))
Community
  • 1
  • 1
Vsevolod Dyomkin
  • 9,343
  • 2
  • 31
  • 36
3

Vsevolod's post inspired me to post a macro as well:

(defmacro defpackage! (package &body options)
  (let* ((classes (mapcan 
                    (lambda (x) 
                      (when (eq (car x) :export-from-classes)
                        (cdr x)))
                    options))
         (class-objs (mapcar #'closer-common-lisp:find-class classes))
         (class-slots (mapcan #'closer-mop:class-slots class-objs))
         (slot-names (mapcar #'closer-mop:slot-definition-name class-slots))
         (slots-with-accessors
           (remove-duplicates (remove-if-not #'fboundp slot-names))))
    (setf options (mapcar
                    (lambda (option)
                      (if (eq (car option) :export)
                        (append option 
                                (mapcar #'symbol-name slots-with-accessors))
                        option))
                    options))
    (setf options (remove-if 
                    (lambda (option)
                      (eq (car option) :export-from-classes))
                    options))
    `(defpackage ,package ,@options)))

To use:

CL-USER> 
(defclass test-class ()
  ((amethod :accessor amethod :initarg :amethod :initform 0)
   (bmethod :reader bmethod :initform 1)))
#<STANDARD-CLASS TEST-CLASS>
CL-USER> 
(closer-mop:ensure-finalized  (find-class 'test-class))
#<STANDARD-CLASS TEST-CLASS>
CL-USER> 
(macroexpand-1 
  `(defpackage! test-package
     (:export "symbol1")
     (:export-from-classes test-class)))
(DEFPACKAGE TEST-PACKAGE
  (:EXPORT "symbol1" "AMETHOD" "BMETHOD"))
T
CL-USER> 

This isn't well tested, and I'm still learning the MOP API, so there may be much better/cleaner ways to achieve the same goal here (especially the fboundp kludge). Also, this only looks for accessor functions on a class. There are also methods that specialize on a class. You could use the MOP to find those as well...

Clayton Stanley
  • 7,513
  • 9
  • 32
  • 46
2

There is a way with the cl-annot package. Its export-slots, export-accessors, export-constructors allow to automatically export them. It works for classes and structures.

For example,

@export-accessors
(defclass foo ()
     ((bar :reader bar-of)
      (bax :writer bax-of)
      (baz :accessor baz-of)))

is equivalent to

(progn
  (export '(bar-of bax-of baz-of))
  (defclass foo ()
     ((bar :reader bar-of)
      (bax :writer bax-of)
      (baz :accessor baz-of))))
Ehvince
  • 17,274
  • 7
  • 58
  • 79
2

Writing export definitions is a chore - especially if structs are involved. While - as other answers show, there can be more sophisticated approaches, here is what I usually do:

  • Write the package and the implementation, the (:export) list being empty in the package definition.
  • Then, I call my little helper function, which lists all fboundp symbols in a package in copy and paste friendly way.
  • Then, I copy the output of my helper function into the (:export) part of the package definition and delete all lines I do not want to export.

Here is my little helper function, which also uses some bits of the accepted answer.

(defun show-all-fboundp-symbols-of-package
    (package-name
     &optional (stream t))
  (let ((pack (find-package package-name)))
    (do-all-symbols (sym pack)
      (when (eql (symbol-package sym) pack)
        (when (fboundp sym)
          (format stream ":~A~%" (symbol-name sym)))))))
BitTickler
  • 10,905
  • 5
  • 32
  • 53