Toerndev
Toerndev

Reputation: 727

Common Lisp: non-nil arguments and their names to alist, how?

I am quite new to Common Lisp and programming, and I'm trying to write a certain function that turns all non-nil args into an alist. The only way I can think of so far is:

(let ((temp nil))
    (if arg1
        (setf temp (acons 'arg1 arg1 nil)))
    (if arg2
        (setf temp (acons 'arg2 arg2 temp)))
    ...
    (if arg20-ish
        (setf temp (acons 'arg20-ish arg20-ish temp)))

    (do-something-with temp))

which does not seem very elegant, it would be messy with many arguments and when these need to be changed. I am looking for a smarter way to do this, both for the sake of writing this particular function and for learning how to think in Lisp and/or functional programming.

The tricky part for me is figuring out how to get the names of the arguments or what symbol to use, without hand coding each case. If &rest provided arg names it would be easy to filter out NILs with loop or mapcar, but since it doesn't, I can't see how to "automate" this.
I'm totally interested in other solutions than the one described, if people think this way is unnatural.

Edit: Below is an example of what I am trying to do:

An object is created, with a non-fixed number of data pairs and some tags, e.g.:

user = "someone"  
creation-time = (get-universal-time)  
color-of-sky = "blue"  
temperature-in-celsius = 32  
language = "Common Lisp"
...  
tags = '("one" "two" "three")

These properties (i.e. key/arg names) could be different each time. The new object will then be added to a collection; I thought the array might work well since I want constant access time and only need a numeric ID.
The collection will hold more and more such custom objects, indefinitely.
I want to be able to quickly access all objects matching any combination of any of the tags used in these objects.
Since the array is supposed to store more and more data over a long period, I don't want to parse every item in it each time I need to search for a tag. Thus I also store the index of each object with a given tag in a hash-table, under the tag name. I have written this function, what I find difficult is figuring out how to collect the data and turn it into an alist or anything that I can easily parse, index, and store.

Upvotes: 2

Views: 363

Answers (2)

Terje Norderhaug
Terje Norderhaug

Reputation: 3689

This macro will define a function that turns its non-nil arguments into an alist bound during execution of the body:

(defmacro defnamed (fun-name alist-sym (&rest args) &body body)
  `(defun ,fun-name (,@args)
     (let ((,alist-sym))
      ,@(mapcar
         (lambda (s)
          `(when ,s
            (push (cons ',s ,s) ,alist-sym)))
         (reverse args))
      ,@body)))

Demonstration:

(defnamed make-my alist (a b c) 
  alist)

(make-my 1 NIL 3)

=> ((A . 1) (C . 3))

Upvotes: 2

Rupert Swarbrick
Rupert Swarbrick

Reputation: 2803

Here's a sort of solution using macros:

(defmacro named-args (fun-name alist-sym (&rest syms) &body body)
  `(defun ,fun-name (&key ,@syms)
     (declare (special ,@syms))
     (let ((,alist-sym
            (loop
               for s in ',syms
               collecting (cons s (symbol-value s)))))
       ,@body)))

You can then use it with something like

(named-args f u (a b c)
  (format t "~A~%" u))

which expands to

(DEFUN F (&KEY A B C)
  (DECLARE (SPECIAL A B C))
  (LET ((U
         (LOOP FOR S IN '(A B C)
            COLLECTING (CONS S (SYMBOL-VALUE S)))))
    (FORMAT T "~A~%" U)))

Finally, calling will give

(f :a 3) => ((A . 3) (B) (C))

Note that we need the special declaration otherwise symbol-value doesn't work (you need a global binding for symbol-value). I couldn't find a way to get rid of that.

Looking at your question again, it looks like you actually don't want the keyword arguments that didn't get passed. In which case you could parse a &rest argument (although that's a flat list, so you'd need to map along it in twos) or you could modify the macro as follows:

(defmacro named-args (fun-name alist-sym (&rest syms) &body body)
  `(defun ,fun-name (&key ,@syms)
     (declare (special ,@syms))
     (let ((,alist-sym
            (loop
               for s in ',syms
               when (symbol-value s)
               collecting (cons s (symbol-value s)))))
       ,@body)))

and then you get

(f :a 3) => ((A . 3))

Upvotes: 2

Related Questions