From: Eric on
On Oct 7, 1:20 pm, Vassil Nikolov <vniko...(a)pobox.com> wrote:
> On Tue, 6 Oct 2009 21:13:50 -0700 (PDT), Eric <gir...(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)))))

This here is exactly what I needed to see, thank you. I haven't quite
grasped it yet, but I will. I was vaguely thinking of something like
that destructuring pattern, too, and its very nice to see it in
action. Thanks for assert (conditions and restarts are probably how I
should be handling everything), and thanks to you both for the further
reading. I appreciate the thorough walkthrough!

Off to study,
Eric


>
>   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?"

From: Joshua Taylor on
> (and (> val x) (< val y))

Only because I was delighted when I found out that similar test in my
own code could be simplified…

(< x val y)
From: Thomas A. Russ on
锁住子 <girzel(a)gmail.com> writes:

> The goal is a macro that creates macros that validates or alters
> values. A couple of very simple examples:

It would be good to have an alteration example to work with as well.
Otherwise...

> 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.

....it seems like you could just use ASSERT.


--
Thomas A. Russ, USC/Information Sciences Institute
From: Thomas A. Russ on
锁住子 <girzel(a)gmail.com> writes:

> 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:

Hmmm. I wonder if it wouldn't just be simpler to have your macro create
functions or just use functions in the first place. The alteration of
value could be simply accomplished using the return value of the
validation function.

--
Thomas A. Russ, USC/Information Sciences Institute
From: Vassil Nikolov on

On 07 Oct 2009 12:11:05 -0700, tar(a)sevak.isi.edu (Thomas A. Russ) said:
> Hmmm. I wonder if it wouldn't just be simpler to have your macro create
> functions or just use functions in the first place. The alteration of
> value could be simply accomplished using the return value of the
> validation function.

Or by passing a suitable thunk, but I think the purpose here is
learning and exercise.

---Vassil.


--
"Even when the muse is posting on Usenet, Alexander Sergeevich?"