Arc Forumnew | comments | leaders | submitlogin
Trouble with loop for link and macro for defop
1 point by evanrmurphy 5550 days ago | 13 comments
I'm new to Lisp and Arc but really loving it so far. I'm trying to make a simple website for shuttle schedules. It has a handful of routes called 10a, 10b etc. which I'm using as symbols:

  (= routes* '(10a 10b 15a 20a 20b 30a 30b 30c 40a 59u))
There's a dummy main page at /shuttle with links for each route:

  (defop shuttle req
    (each r routes* (link r (nbsp))))
This displays the links fine, but they don't point to /10a, /10b, etc. as they should. Instead they all point to /%C2%A0 . Is this some evaluation of the character 'r' instead of my route symbols?

Then I'm trying to make a page for each route. Instead of writing 10 identical defops, I tried to make a macro (rdefop) so I could loop through writing a defop for each route:

  (mac rdefop (r)
    `(defop ,r req
        (pr "Schedule for " ,r " goes here")))

  (each r routes*
    (rdefop r))
For each of the symbols in routes* , I hoped this would generate a page at the corresponding URL. So I wanted to see a page at /10a with text "Schedule for 10a goes here", one at /10b with text "Schedule for 10b goes here", etc. The above code instead creates a page at /r with text "Schedule for 59u goes here" (59u is the final symbol in routes*).

I'm clearly having newb confusion with simple evaluations and macroexpansions, but I've been debugging for hours and can't get it working correctly. If anyone has advice for how to fix the above I'd greatly appreciate it. Thanks so much!



2 points by conanite 5550 days ago | link

A quick way to figure out what's going wrong is to try it at the repl ...

  arc> (= routes* '(10a 10b 15a 20a 20b 30a 30b 30c 40a 59u))
  (10a 10b 15a 20a 20b 30a 30b 30c 40a 59u)
  arc> (each r routes* (link r (nbsp)))
  &nbsp;<a href="&nbsp;">10a</a>&nbsp;<a href="&nbsp;">10b</a>&nbsp;<a href="&nbsp;">15a</a>&nbsp;<a href="&nbsp;">20a</a>&nbsp;<a href="&nbsp;">20b</a>&nbsp;<a href="&nbsp;">30a</a>&nbsp;<a href="&nbsp;">30b</a>&nbsp;<a href="&nbsp;">30c</a>&nbsp;<a href="&nbsp;">40a</a>&nbsp;<a href="&nbsp;">59u</a>nil
  arc> 
The <a href="&nbsp;"> stands out like a sore thumb in this case.

-----

2 points by thaddeus 5550 days ago | link

I think you intended to do this:

  (defop shuttle req
    (each r routes* (link r)(nbsp)))
And don't ask me why the following works - it's a brutal hack!

  (= routes* '("10a" "10b" "15a" "20a" "20b" "30a" "30b" "30c" "40a" "59u"))

  (defop shuttle req
    (each r routes* (link r)(nbsp)))

  (def rpage (r)
     (pr r))

  (mac rdefop (rs)
    (eval `'(defop ,(fromstring  rs (read)) req 
	          (rpage ,rs))))

  (each r routes*
    (eval `(rdefop ,r)))

[edit] corrected link as per aw's post.

-----

1 point by thaddeus 5549 days ago | link

Curious if any of the more advanced folks on this board can show how to properly do the above code (such that I can learn from). Or is the above code not so bad after all? Thanks.

-----

1 point by shader 5548 days ago | link

Why do you need the eval in rdefop? What am I missing?

-----

1 point by thaddeus 5548 days ago | link

I am not sure. It didn't work for me when I took it out.

-----

3 points by fallintothis 5548 days ago | link

You don't really need eval in the macro. In the original,

  (each r routes*
    (rdefop r))
macroexpands into

  (each r routes*
    (defop r req (pr "Schedule for " r " goes here")))
without evaluating what r is bound to --- you'll just get an /r handler.

So, using eval makes sure r gets evaluated. But you do this haphazardly between two points:

  (mac rdefop (rs)
    (eval `'(defop ,(fromstring  rs (read)) req
                  (rpage ,rs))))

  (each r routes*
    (eval `(rdefop ,r)))
We only need to eval once.

  (= routes* '(10a 10b 15a 20a 20b 30a 30b 30c 40a 59u))

  (def rpage (r)
    (pr r))

  (mac rdefop (r)
    `(defop ,r req (rpage ',r)))

  (each r routes*
    (eval `(rdefop ,r)))
Thus, instead of doing (defop r), it's kind of like we're doing (defop (eval r)). That is, instead of expanding into

  (defop r req (rpage 'r))
we're expanding into

  (defop 10a req (rpage '10a)) ; when r == '10a
  (defop 10b req (rpage '10b)) ; when r == '10b
  ; etc
We could've done the eval inside the macro, too, but that's often a sign you're doing something wrong --- macros are usually there to not evaluate their arguments. So we should probably use a function.

However, defop itself is a macro, so the first parameter (the op's name) won't be evaluated regardless. We still need eval.

  (= routes* '(10a 10b 15a 20a 20b 30a 30b 30c 40a 59u))

  (def rpage (r)
    (pr r))

  (def rdefop (r)
    (eval `(defop ,r req (rpage ',r))))

  (each r routes*
    (rdefop r))
Since this approach seems to require eval regardless, we should just look for a better solution. aw's works nicely.

Some other nitpicks over your rewrite (hey, you asked!):

1) Unless you need strings for some reason, you can probably default to symbols (they're a bit easier to use).

  ; Instead of

  (= routes* '("10a" "10b" "15a" "20a" "20b" "30a" "30b" "30c" "40a" "59u"))

  ; we could use

  (= routes* '(10a 10b 15a 20a 20b 30a 30b 30c 40a 59u))
2) Proper spacing & indentation saves lives. :)

  ; Instead of

  (defop shuttle req
    (each r routes* (link r)(nbsp)))

  ; why not

  (defop shuttle req
    (each r routes* (link r) (nbsp)))

  ; or even

  (defop shuttle req
    (each r routes*
      (link r)
      (nbsp)))
3) Though using symbols renders this point moot, fromstring is unnecessary to simply (read) from a string, since Arc's definition of read is

  (def read ((o x (stdin)) (o eof nil))
    (if (isa x 'string) (readstring1 x eof) (sread x eof)))
So, instead of

  (fromstring rs (read))
you can use

  (read rs)
If your goal is just to turn a string into a symbol, you should use

  (sym rs)
This is an important distinction. e.g.,

  arc> (sym "abc")
  abc
  arc> (read "abc")
  abc
  arc> (sym "(a b c)")
  |(a b c)|
  arc> (type that)
  sym
  arc> (read "(a b c)")
  (a b c)
  arc> (type that)
  cons

-----

1 point by evanrmurphy 5548 days ago | link

Very instructive. Thank you for doing such a thorough analysis.

',r from your snippet

  (mac rdefop (r)
    `(defop ,r req (rpage ',r)))
was a realization for me. Never thought of quoting a comma'd symbol in a backquoted expression before, but I like knowing it's possible. Do you find yourself doing this much, or is there usually something simpler like aw's solution available to make it unnecessary?

-----

2 points by fallintothis 5548 days ago | link

Happy to help.

Oh, yes, quoted-unquotes are done pretty often.

  $ grep -c "'," {arc,code,html,srv,app,news}.arc
  arc.arc:17
  code.arc:1
  html.arc:6
  srv.arc:9
  app.arc:1
  news.arc:7
For example, the Arc built-in obj is a macro that makes a hash table with automatically-quoted keys.

  arc> (= h (obj a 1 b 2))
  #hash((b . 2) (a . 1))
  arc> (h 'a)
  1
  arc> (h 'b)
  2
It's defined like so:

  (mac obj args
    `(listtab (list ,@(map (fn ((k v))
                             `(list ',k ,v)) ; note we unquote k, then quote it
                                             ; so we're quoting the value of k
                           (pair args)))))
That way, the above (obj a 1 b 2) expands into

  (listtab (list (list 'a 1) (list 'b 2)))

-----

1 point by evanrmurphy 5548 days ago | link

Thanks again.

-----

1 point by thaddeus 5548 days ago | link

dito

-----

2 points by aw 5550 days ago | link

The second argument to "link" specifies the URL that the link goes to. So you might try

  (link r (+ "/" r))
which will make the URL "/10a" for route 10a.

If all your pages are at the same level and using just "10a" for the URL would work OK, then you could use

  (link r)
because if you call "link" with one argument it will be used for both the text and the URL.

-----

2 points by aw 5550 days ago | link

For your second question, we could work on getting your macro defining macro to work, if you wanted. For this particular task, personally I'd do something simpler: I'd make the link like this:

  (link r (+ "/schedule?route=" r))
and then have

  (defop schedule req
    (pr "Schedule for " (arg req "route") " goes here"))

-----

1 point by evanrmurphy 5550 days ago | link

Very helpful, all of you. Thanks again for your time and the great advice.

@conanite This will definitely help me take advantage of the repl better in the future.

@thaddeus

  > I think you intended to do this: [...]
Yes, exactly!

  > And don't ask me why the following works - it's a brutal hack! [...]
Thanks for posting this. I'm realizing more and more how much better it is to have a brutal hack that works than some thoughtful code that doesn't.

@aw I used a variation of your code (with ?route=) in my solution. Much more elegant than what I was trying to do, thank you.

-----