Pergunta

how would you / should you export symbols from a package, when you have not yet created them at the time of calling the defpackage macro?

(defpackage :package-a
  (:use :cl)
  (:export :fruit-type :animal-type :orange :apple :peach :cat :dog))

(deftype fruit-type () '(member ORANGE APPLE PEACH))
(deftype animal-type () '(member CAT DOG))

(defparameter *other-symbol-names*
  '("A1" "A2" "B1" "B2")) ;imagine a longer list here
                          ;with names generated by a function

(defparameter *other-symbols*
  (mapcar #'(lambda (sym-name)
          (import (make-symbol sym-name))
          (find-symbol sym-name))
      *other-symbol-names*))

(mapcar #'export *other-symbols*)

(setf A1 32 A2 33 B1 34 B2 35)

also there is another package

(defpackage :package-b
  (:use :cl :package-a))
(in-package :package-b)
(format nil "~a ~a ~a ~a" |A1| |A2| |B1| |B2|)

I have read in "The Complete Idiot’s Guide to Common Lisp Packages" that "Now that you’ve learned all about the myriad functions and macros that can be used to manipulate packages you shouldn’t really be using any of them. Instead, all of the functionality of IMPORT, EXPORT, SHADOW, etc. is all rolled up in a single macro called DEFPACKAGE, which is what you should use for real (non- prototype) code."

Is there a code smell in my above code? Also, how would you export the other symbols (cat dog animal-type, etc. -- there are many of them) to avoid duplication?

Foi útil?

Solução

It's hard to say much without knowing more about your intent and requirements, but in many situations it would be better to have one or more hash tables (or similar) which contain your dynamically generated objects, and then export symbol(s) for your hash table(s).

Here's a hand-wavy example of how this can work. If you can edit and add some more information about your requirements and constraints I'll see if I can be more help.

(in-package :cl)

(defpackage :package-a
  (:use :cl)
  (:export *objects* put get)
  (:shadow get))

(in-package :package-a)

(defvar *objects* (make-hash-table)
  "Container for dynamically generated objects we want to expose to the
  package's user.")

(defun put (name obj)
  (setf (gethash name *objects*) obj))

(defun get (name &optional default)
  (gethash name *objects* default))

;; Your code can put arbitrary objects into the hash table
(put :foo (lambda () :a-thunk))
(put :bar (lambda () :another))

;; And your users can retrieve them
(in-package :cl-user)
(use-package :package-a)
(funcall (get :foo)) ;; => :a-thunk

I used keywords for the names rather than symbols because keywords aren't local to packages (or, more specifically, they're all local to the keyword package. If you were to instead use 'foo and 'bar you'd be back to needing to export those symbols, or your user would need to use the package designator when refer to them (e.g. (get 'package-a::foo)).

You can also use strings as keys, though in that case you would want to create the table with (make-hash-table :test 'equal). The default hash table test is #'eql, which doesn't compare strings appropriately. Comparing keywords with #'eql is faster than comparing strings with #'equal (because keywords are a simple pointer comparison, as opposed to the character-by-character comparison necessary for strings) but the difference is likely insignificant unless you have specific reason to think otherwise.

This approach provides a better interface for your users, because now your have defined entry points, the opportunity for docstrings, defaults, and easier exploration at the REPL.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top