CL-USER
CL-USER

Reputation: 786

Programmatically generating symbol macros

I've got a data structure that consists of two parts:

  1. A hash table mapping symbols to indices
  2. A vector of vectors containing data

For example:

(defparameter *h* (make-hash-table))
(setf (gethash 'a *h*) 0)
(setf (gethash 'b *h*) 1)
(setf (gethash 'c *h*) 2)

(defparameter *v-of-v* #(#(1 2 3 4)       ;vector a
                         #(5 6 7 8)       ;vector b
                         #(9 10 11 12)))  ;vector c

I'd like to define a symbol macro to get at vector a without going through the hashmap. At the REPL:

(define-symbol-macro a (aref *v-of-v* 0))

works fine:

* a
#(1 2 3 4)

but there could be potentially many named vectors, and I don't know what the mappings will be ahead of time, so I need to automate this process:

(defun do-all-names ()
  (maphash #'(lambda (key index)
           (define-symbol-macro key (aref *v-of-v* index)))
       *h*))

But that does nothing. And neither does any of the combinations I have tried of making do-all-names a macro, back-quote/comma templates, etc. I am beginning to wonder if this doesn't have something to do with the define-symbol-macro itself. It seems a little used feature, and On Lisp only mentions it twice. Not too many mentions here nor elsewhere either. In this case I'm using SBCL 2.1

Anyone have any ideas?

Upvotes: 0

Views: 239

Answers (2)

Rainer Joswig
Rainer Joswig

Reputation: 139411

You need something like above to do it at runtime:

(defun do-all-names ()
  (maphash #'(lambda (key index)
               (eval `(define-symbol-macro ,key (aref *v-of-v* ,index)))
           *h*))

DEFINE-SYMBOL-MACRO is a macro and does not evaluate all its arguments. So you need to generate a new macro form for each argument pair and evaluate it.

The other way to do it, usually at compile time, is to write a macro which generates these forms on the toplevel:

(progn
  (define-symbol-macro a (aref *v-of-v* 0))
  (define-symbol-macro b (aref *v-of-v* 1))
  ; ....
  )

Upvotes: 1

digikar
digikar

Reputation: 588

I'm not too sure on what you mean by "I don't know what the mappings will be ahead of time".

You could do something like:

(macrolet ((define-accessors ()
             `(progn
                ,@(loop for key being the hash-keys of *h*
                        collect
                        `(define-symbol-macro ,key (aref *v-of-v* ,(gethash key *h*)))))))
  (define-accessors))

If you know you do not require global access, then, you could do:

(defmacro with-named-vector-accessors (&body body) ; is that the name you want?
  `(symbol-macrolet (,@(loop for key being the hash-keys of *h*
                             collect `(,key (aref *v-of-v* ,(gethash key *h*)))))
     ,@body))

;;; Example Usage:
(with-named-vector-accessors
  (list a b c)) ;=> (#(1 2 3 4) #(5 6 7 8) #(9 10 11 12))

Also,

  1. If you know *h* and the indices each symbol maps to at macroexpansion time, the above works.

  2. If you know *h* at macroexpansion but the indices each symbol maps to will change after macroexpansion, you will want to collect (,key (aref *v-of-v* (gethash ,key *h*))).

PS: If you find loop ugly for hash-tables, you could use the iterate library with the syntax:

(iter (for (key value) in-hashtable *h*)
  (collect `(,key (aref *v-of-v* ,value))))

Upvotes: 0

Related Questions