Arc Forumnew | comments | leaders | submitlogin
Two part noob question
10 points by projectileboy 6157 days ago | 11 comments
1) As an exercise, I was trying to port Peter Seibel's unit testing stuff from Chapter 8 of Practical Common Lisp, and it made me realize I don't know how to write macros. I want a macro that turns this:

    (check
       (is (+ 1 2) 3)
       (is (+ 4 5) 9))
...into this:

    (do
        (report-result (is (+ 1 2) 3) '(is (+ 1 2) 3))
        (report-result (is (+ 4 5) 9) '(is (+ 4 5) 9)))
..using this macro:

    (mac check args
       `(do
          ,(each arg (list ,@args)
             `(report-result ,arg ',arg))))
Which, of course, doesn't work. Does anyone out there have the patience to explain why?

2) I'm going to want to ask more of these types of questions, but is this the right forum? I don't want to spam the community.



17 points by absz 6157 days ago | link

The macro you want is

  (mac check args
    `(do
        ,@(map [list 'report-result _ `',_] args)))
There are a couple of things wrong with your macro. The first is that your (list ,@args) is inside ,(each ...) which is inside `(do ...); you can only have as many unquotes (,s or ,@s) as you have quasiquotes (`s), and you have two within one. The second is that "each" is only run for its side-effects. The return value of "each" is always nil. Thus, even if your macro worked, it would expand to (do nil), which isn't what you want. To replace "each", you want (map func lst), which returns a list where func has been applied to each element of lst, e.g. (map - '(1 2 3)) returns '(-1 -2 -3). In my macro, the function returns a list of the form (report-result ARGUMENT 'ARGUMENT); the `',_ construct means "quote the value of _," since ,_ is within a `. Splicing this (map ...) into the (do ...) block will give you what you want. Is that clear?

Also, a handy tip for debugging macros: (macex1 '(an-expression ...)) will expand the first macro call in (an-expression), which can help you see what's going wrong.

I can't tell you if this is the right place to ask these questions, but having some place for them would definitely be a good thing. I'm usually happy to answer them, though.

-----

5 points by projectileboy 6157 days ago | link

Enormously helpful. Thank you. Coming from Blub world, it's been hard for me to think functionally - making a distinction between returning values and side effects.

I don't understand your first point however, as this is a perfectly valid macro:

(mac double (x) `(+ ,x ,x))

-----

8 points by ryantmulligan 6157 days ago | link

Let me explain the "two within one" problem that you may be saying that you do not see.

  (mac check args                                 ;;no qq's
       `(do                                       ;; ` one qq
          ,(each arg (list ,@args)                ;; ,(,@) two escapes
             `(report-result ,arg ',arg))))       ;; `(, ',) two qq one escape
So on the line

  ,(each arg (list ,@args) 
you are doubly escaping something that is only quoted once.

-----

0 points by projectileboy 6157 days ago | link

Gotcha. Gracias.

-----

3 points by almkglor 6156 days ago | link

Nitpick. This is not a good definition (valid, but not good). The problem is something like this:

  (double (prn 3))
Try the above in your repl after entering the mac definition; then consider what must be done in order to protect the x. For example, you might notice that the macros in arc.arc have a lot of (w/uniq (...) `(let ...)) forms, even the arguably simpler ones.

-----

6 points by ryantmulligan 6157 days ago | link

Great reply, you are exactly what the Arc community needs to succeed!

-----

1 point by binx 6157 days ago | link

(mac check args `(do ,@(each arg args `(report-result ,arg arg))))

might work, I didn't test.

-----

1 point by absz 6157 days ago | link

That's not going to work, as I observed above, because (each ...) only runs for the side-effects, and will always return nil.

-----

1 point by kennytilton 6157 days ago | link

Right, but I think the different approach to the splicing was sound and simpler for noobs:

  (def report-result (result form)
    (prn)
    (prn form)
    (prs '-> result)(prn)
    (prn))

  (mac check args
    `(do ,@(do (map (fn (test)
                    `(report-result ,test ',test)) args))))
I think I transliterated to the _ form accurately, but this commented out bit gives me an error from CAR.

;;; so why doesn't this work? ;;;(mac check args ;;; `(do ,@(do (map [`(report-result ,_ ',_)] args))))

Anyway, this then produces the expected results:

  (check
   (is (* 6 9) 42)
   (is (* 6 9) (coerce "42" 'int 13)))
The above should fail the first, pass the second, tho Adams denied knowing that was what he was going on about.

-----

3 points by almkglor 6156 days ago | link

  [`(report-result ,_) ',_]
becomes:

  (fn (_) (`(report-result ,_) ',_))
If you notice, whenever a macro in arc.arc wants to use `(), it avoids using the [... _ ...] syntax for it.

-----

2 points by kennytilton 6156 days ago | link

Oops, that extra "do" wrapping just the map was left over from debugging the macro and can be chopped. Note to noobs: this is also a macro-writing tip to remember, one can put debugging print statements that run as a macro gets expanded to help debug and even learn how to write macros.

-----