[alexandria-devel] Anonymous modify macros through `CALLF'
Nikodemus Siivola
nikodemus at random-state.net
Wed Jan 16 11:27:21 UTC 2008
On 9/13/07, Tobias C. Rittweiler <tcr at freebits.de> wrote:
> Alexandria seems to contain a lot of modify macro definitions. Which is
> no wonder, as those are quite practical: They're concise, and
> simultaneously guarantee that their arguments are only evaluated once.
>
> Inspired from CL.el in Emacs, I grew quite fond of CALLF which is a
> generalized modify macro, or if you look from the right angle, an
> anonymous modify macro facility.
>
> Instead of having to globally define modify macros each time you want
> one for a certain function, you simply use CALLF instead:
>
> (callf #'append place l1 l2 l3 ...) == (appendf place l1 l2 l3)
>
> (callf #'coerce place 'foo) == (coercef place 'foo)
>
> and so on.
It may be just never having seen this before, but I admit this makes
me squint a bit. Having now fooled a bit with alternative ways to write
eg. DROP, I have to admit these are appealingly simple to use.
If nothing else, I think CALLF-N should index arguments from zero -- unless
there is a specific reason for starting from 1? (PROG1 / PROG2 analogue
actually just came to mind, maybe 1 is right after all.)
...then again, we _are_ going to run the pruning pass over Alexandria.
So I'll probably push these soonish, and we can see how using them feels.
Cheers,
-- Nikodemus
> CALLF is actually just an abbreviation for the even more general macro
> CALLF-N:
>
> (callf-n n function &rest args)
>
> which takes the Nth argument to be the place. I.e.
>
> (callf #'f place x y z) == (callf-n 1 #'f place x y z)
>
> This is important if you want to use things like REMOVE or DELETE where
> the place is the second argument. I.e. something almost equivalent to
> Erik Naggum's DROP could be defined like:
>
> (defmacro drop (object place &rest keys &key key test test-not)
> "Drop a particular OBJECT from list in PLACE.
> (Intended as counterpart to PUSH.)"
> (declare (ignore key test test-not))
> `(callf2 #'delete ,object ,place :count 1 , at keys))
>
> where CALLF2 is equivalent to (CALLF-N 2); CALLF and CALLF2 are
> abbreviations for the most common cases, so they're specially provided.
>
> As you can see, these macro also aid macro programming because they
> relieve you from having to mess with GET-SETF-EXPANSION &c.
>
> I place the implementation below into the public domain to be included
> into the Alexandria library.
>
> Notice that the implementation uses another utility BREAKUP which
> (together with its sibling BREAKUP-IF) should also be included into
> Alexandria.
>
> -T.
>
>
> (defun breakup (n list)
> "Breaks LIST into two pieces at position N where the (NTH n) element
> will become the first element of the second piece. Both pieces are
> returned in a list.
>
> If N < 0, treat N as if 0.
>
> If N >= (length LIST), return NIL as second piece.
>
> Examples:
>
> (breakup 0 '(0 1 2 3 4)) => (NIL (0 1 2 3 4))
> (breakup 2 '(0 1 2 3 4)) => ((0 1) (2 3 4))
> (breakup 9 '(0 1 2 3 4)) => ((0 1 2 3 4) NIL)
> "
> (let ((front nil))
> (dotimes (i n)
> (when (null list) (return))
> (push (car list) front)
> (setf list (cdr list)))
> (list (nreverse front) list)))
>
> (defun breakup-if (predicate list)
> "Breaks LIST into two pieces at the first element of which
> PREDICATE returns T.
>
> Examples:
>
> (breakup-if #'oddp '(2 4 6 7 8 10)) => ((2 4 6) (7 8 19))
> (breakup-if #'evenp '(2 4 6 7 8 10)) => (NIL (2 4 6 7 8 10))
> "
> (loop with tail = nil
> for (x . rest) on list
> if (not (funcall predicate x))
> collect x into front
> else
> do (setq tail (cons x rest))
> (loop-finish)
> finally (return (list front tail))))
>
>
> (defmacro callf-n (n function &rest args &environment env)
> "CALLF-N represents an anonymous modify macro facility.
>
> The Nth argument (starting from 1) of ARGS is considered to be a `place'.
> This place is set to `(FUNCTION arg1 ... arg{N-1} place arg{N+1} ...)'.
>
> Examples:
>
> (appendf place l1 l2 ...) == (callf-n 1 #'append place l1 l2 ...)
>
> (progn
> (defvar *foo* (list 1 2 (list 3 4 4 4 5) 6 7))
> (callf-n 2 #'remove 4 (third *foo*))
> *foo*) ==> (1 2 (3 5) 6 7)
> "
> (destructuring-bind ((&rest frontargs) (place &rest tailargs)) (breakup (1- n) args)
> (multiple-value-bind (gvars vals gstorevars setter getter)
> (get-setf-expansion place env)
> (when (second gstorevars)
> (error "CALLF does not support setting multiple values via the (VALUES ...) place."))
> (let ((gstorevar (first gstorevars)))
> `(let ,(mapcar #'list gvars vals)
> (let ((,gstorevar (funcall ,function , at frontargs ,getter , at tailargs)))
> ,setter)
> )))))
>
> (defmacro callf (function &rest args)
> "Abbreviation for (CALLF-N 1 FUNCTION args..)."
> `(callf-n 1 ,function , at args))
>
> (defmacro callf2 (function &rest args)
> "Abbreviation for (CALLF-N 2 FUNCTION args..)."
> `(callf-n 2 ,function , at args))
>
> --
> Diese Nachricht wurde auf Viren und andere gefaerliche Inhalte untersucht
> und ist - aktuelle Virenscanner vorausgesetzt - sauber.
> Freebits E-Mail Virus Scanner
>
> _______________________________________________
> alexandria-devel mailing list
> alexandria-devel at common-lisp.net
> http://common-lisp.net/cgi-bin/mailman/listinfo/alexandria-devel
>
More information about the alexandria-devel
mailing list