Prev: local-time on Clozure CL windows vista 64 Can't resolve foreign symbol "gettimeofday"
Next: DEFSTRUCT and lexical environment
From: 锁住子 on 6 Oct 2009 23:36 I'm trying to write a macro which returns macros (I've been reading the relevant bits of On Lisp), and running into nested backquote problems. The goal is a macro that creates macros that validates or alters values. A couple of very simple examples: (val-macro between (val x y) (and (> val x) (< val y)) ("%s is not between %s and %s" val x y)) (val-macro is-a-dog (val) (string= val "dog") ("%s is not a dog" val)) This looks simple to the point of being meaningless, but it gets more complicated later. So I need a macro that accepts a name, an argument list, a single-form predicate, and a format-style error message in case the value does not validate. Here's what I've got so far. The backquoting has confused me so badly that I've just left it out I don't know how to handle arg-list, predicate, args and error-clause within the inner macro. I also can't figure out if "result" is vulnerable to variable capture. Any direction for a poor newbie? Thanks in advance, Eric (defmacro defval (name arg-list predicate error-clause) `(defmacro ,name (&rest args) `(let ((result (lambda arg-list predicate args))) (if result (values t (first args)) (values nil (format nil error-clause))))))
From: Vassil Nikolov on 6 Oct 2009 23:52 So would it work for you to have (defval between (val x y) (and (> val x) (< val y)) ("~S is not between ~S and ~S" val x y)) expand into (DEFUN between (val x y) (IF (and (> val x) (< val y)) (VALUES T val) (VALUES NIL (FORMAT NIL "~S is not between ~S and ~S" val x y)))) and (defval is-a-dog (val) (string= val "dog") ("%s is not a dog" val)) expand into (DEFUN is-a-dog (val) (IF (string= val "dog") (VALUES T val) (VALUES NIL (FORMAT NIL "%s is not a dog" val)))) (note that DEFUN is a macro, too...); or if the above doesn't work, would you elaborate why? ---Vassil. -- "Even when the muse is posting on Usenet, Alexander Sergeevich?"
From: Eric on 7 Oct 2009 00:13 On Oct 7, 11:52 am, Vassil Nikolov <vniko...(a)pobox.com> wrote: > So would it work for you to have > > (defval between (val x y) > (and (> val x) (< val y)) > ("~S is not between ~S and ~S" val x y)) > > expand into > > (DEFUN between (val x y) > (IF (and (> val x) (< val y)) > (VALUES T val) > (VALUES NIL (FORMAT NIL "~S is not between ~S and ~S" val x y)))) Ooops, my python was showing. It would, for this case. My only reservation is that in the future I'd like to expand this so it's not just a true/false test, but that users can write validators that both validate and/or alter val somehow. I was using the lambda because it seemed like it would be easy to add this functionality later the lambda could be altered to return the new version of val, instead of simply t/nil. Since this is partly a learning exercise, and you've been kind enough to answer so far, maybe you wouldn't mind posting a couple of different solutions :) I'll still eventually want to know how to properly nest backquotes! Thanks, Eric > > and > > (defval is-a-dog (val) > (string= val "dog") > ("%s is not a dog" val)) > > expand into > > (DEFUN is-a-dog (val) > (IF (string= val "dog") > (VALUES T val) > (VALUES NIL (FORMAT NIL "%s is not a dog" val)))) > > (note that DEFUN is a macro, too...); or if the above doesn't work, > would you elaborate why? > > ---Vassil. > > -- > "Even when the muse is posting on Usenet, Alexander Sergeevich?"
From: D Herring on 7 Oct 2009 01:04 Eric wrote: > I'll still eventually want to know how to properly nest backquotes! Nested backquotes require nested commas. Something like `(out `(in ,in-arg ,,out-arg) ,out-arg2) The CLHS has a good intro, but doesn't appear to show nested forms http://www.lispworks.com/documentation/HyperSpec/Body/02_df.htm Search for ,, on http://www.gigamonkeys.com/book/macros-defining-your-own.html to see a hairy example. And perhaps the most popular macro reference is http://www.paulgraham.com/onlisp.html Chapter 16 is "Macro-Defining Macros". See also note #266 in the back. Later, Daniel
From: Vassil Nikolov on 7 Oct 2009 01:20
On Tue, 6 Oct 2009 21:13:50 -0700 (PDT), Eric <girzel(a)gmail.com> said: > ... > It would, for this case. My only reservation is that in the future I'd > like to expand this so it's not just a true/false test, but that users > can write validators that both validate and/or alter val somehow. I > was using the lambda because it seemed like it would be easy to add > this functionality later – the lambda could be altered to return the > new version of val, instead of simply t/nil. I see---I should have recognized ASSERT the first time: with (defval between (val x y) (and (> val x) (< val y)) ("~S is not between ~S and ~S" val x y)) the form (between u a b) is very much like (assert (and (> u a) (< u b)) (u) "~S is not between ~S and ~S" u a b) Let's say that, for now, DEFVAL will do exactly that (making it return the value of the first argument when the test is successful is left as an exercise). Let's take the uninteresting road for the sake of illustration and make DEFVAL define its first argument as a macro which expands into an ASSERT form. The interesting road, not using ASSERT, will be left as another exercise... So we want BETWEEN to expand as shown above, i.e. we want (defmacro between (val x y) `(assert ((lambda (val x y) (and (> val x) (< val y))) ,@(list val x y)) (,val) "~S is not between ~S and ~S" ,@(list val x y))) Indeed, a quick test shows * (macroexpand-1 '(between u a b)) (ASSERT ((LAMBDA (VAL X Y) (AND (> VAL X) (< VAL Y))) U A B) (U) "~S is not between ~S and ~S" U A B) which is what we want. So, the above DEFMACRO form should be the macroexpansion of the DEFVAL form given near the top of this post. Looking at where the variable parts of the DEFVAL form appear as variable parts of the resulting DEFMACRO form, and temporarily "renaming" backquote to tilde and comma to less-than in order to distinguish between the inner and the outer backquoting, we arrive at (defmacro defval (name params test condition-and-args) `(defmacro ,name ,params ~(assert ((lambda ,params ,test) <@(list ,@params)) (<,(first params)) ,(first condition-and-args) <@(list ,@(rest condition-and-args))))) Now, after replacing the tilde with a backquote and the less-than signs with commas in the above, _and_ adding a "magical" comma-quote prefix where the inner backquote would "shadow" the outer one, we obtain (defmacro defval (name params test condition-and-args) `(defmacro ,name ,params `(assert ((lambda ,',params ,',test) ,@(list ,@params)) (,,(first params)) ,',(first condition-and-args) ,@(list ,@(rest condition-and-args))))) and after evaluating the resulting definitions (the preceding DEFMACRO form and the DEFVAL form at the top), we have again * (macroexpand-1 '(between u a b)) (ASSERT ((LAMBDA (VAL X Y) (AND (> VAL X) (< VAL Y))) U A B) (U) "~S is not between ~S and ~S" U A B) which is what we want. As you probably notice, we have to "take apart" some of the arguments of the DEFVAL macro in a couple of places. To simplify that, macros provide _destructuring_, here used for the second and fourth subforms of the DEFVAL form: (defmacro defval (name (place &rest params) test (condition &rest args)) `(defmacro ,name (,place ,@params) `(assert ((lambda ,'(,place ,@params) ,',test) ,@(list ,place ,@params)) (,,place) ,',condition ,@(list ,@args)))) To show all this in action, here is a transcript of what we have arrived at eventually: * (defmacro defval (name (place &rest params) test (condition &rest args)) `(defmacro ,name (,place ,@params) `(assert ((lambda ,'(,place ,@params) ,',test) ,@(list ,place ,@params)) (,,place) ,',condition ,@(list ,@args)))) DEFVAL * (defval between (val x y) (and (> val x) (< val y)) ("~S is not between ~S and ~S" val x y)) BETWEEN * (macroexpand-1 '(between u a b)) (ASSERT ((LAMBDA (VAL X Y) (AND (> VAL X) (< VAL Y))) U A B) (U) "~S is not between ~S and ~S" U A B) T * (defvar *x* 20) *X* * (between *x* 1 10) debugger invoked on a SIMPLE-ERROR in thread #<THREAD "initial thread" {10025DEC11}>: 20 is not between 1 and 10 Type HELP for debugger help, or (SB-EXT:QUIT) to exit from SBCL. restarts (invokable by number or by possibly-abbreviated name): 0: [CONTINUE] Retry assertion with new value for *X*. 1: [ABORT ] Exit debugger, returning to top level. (SB-KERNEL:ASSERT-ERROR ((LAMBDA (VAL X Y) (AND (> VAL X) (< VAL Y))) *X* 1 10) (*X*) "~S is not between ~S and ~S") 0] continue The old value of *X* is 20. Do you want to supply a new value? (y or n) y Type a form to be evaluated: 5 NIL Disclaimer: any errors in the above are entirely this time of the night's fault... I would suggest that you redo the above for practice (which, as the saying goes, is one third of the way to Carnegie Hall...). * * * Note: Guy Steele's _Common Lisp: the Language_, second edition, available on the web, has an appendix in which he treats nested backquoting in significant detail. ---Vassil. -- "Even when the muse is posting on Usenet, Alexander Sergeevich?" |