Common Lisp экспортирует символы из пакетов
Есть ли короткий способ экспортировать все символы из пакета или это единственный способ сделать это в defpackage
, Я вообще пишу свой код в файл foo.lisp
который обычно начинается с (in-package :foo)
и поместите определение пакета в файл package.lisp
который обычно включает в себя что-то вроде этого:
(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))
Мой вопрос: проектирование интерфейса просто с использованием некоторых глобальных переменных и функций иногда может быть неадекватным, и вам придется экспортировать некоторые структуры. В этом случае, если вы не просто экспортируете функции доступа этой структуры, вы не можете манипулировать объектами этой структуры. Итак, есть ли простой способ добиться этого эффекта без ручного экспорта всех этих функций доступа?
5 ответов
Как только пакет создан и все символы в нем созданы, например, загрузив код, который реализует пакет, вы можете экспортировать любые символы, которые вам нравятся, например, экспортировать все:
(do-all-symbols (sym (find-package :foo)) (export sym))
Вы, вероятно, будете счастливы с
(let ((pack (find-package :foo)))
(do-all-symbols (sym pack) (when (eql (symbol-package sym) pack) (export sym))))
который не будет пытаться реэкспортировать все из использованных пакетов.
Оценивая код, расширенный макросом, я получаю сообщение об ошибке для последнего nil в форме defclass, если не указана опция класса и дополнительные кавычки в качестве символов функции экспорта должны быть заключены в кавычки. Вот исправленная версия, которая работает на моей обычной системе lisp (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))
Пост Всеволода вдохновил меня также написать макрос:
(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)))
Использовать:
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>
Это не очень хорошо проверено, и я все еще изучаю MOP API, поэтому здесь могут быть гораздо лучшие / более чистые способы достижения той же цели (особенно fboundp kludge). Кроме того, это ищет только функции доступа в классе. Есть также методы, которые специализируются на классе. Вы можете использовать СС, чтобы найти их, а также...
Написание определений экспорта — рутинная работа, особенно если речь идет о структурах. Хотя, как показывают другие ответы, могут быть и более сложные подходы, вот что я обычно делаю:
- Напишите пакет и реализацию, список в определении пакета пуст.
- Затем я вызываю свою маленькую вспомогательную функцию, которая перечисляет все
fboundp
символы в пакете удобным для копирования и вставки способом. - Затем я копирую вывод моей вспомогательной функции в
(:export)
часть определения пакета и удалите все строки, которые я не хочу экспортировать.
Вот моя маленькая вспомогательная функция, которая также использует некоторые биты принятого ответа.
(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)))))))
Есть способ с пакетом cl-annot. этоexport-slots
, export-accessors
, export-constructors
позволяют автоматически экспортировать их. Это работает для классов и структур.
Например,
@export-accessors
(defclass foo ()
((bar :reader bar-of)
(bax :writer bax-of)
(baz :accessor baz-of)))
эквивалентно
(progn
(export '(bar-of bax-of baz-of))
(defclass foo ()
((bar :reader bar-of)
(bax :writer bax-of)
(baz :accessor baz-of))))