Arc Forumnew | comments | leaders | submitlogin
Reducing Parentheses
4 points by nlavine 5851 days ago | 16 comments
It seems like there's a very common pattern in arc (and lisp in general?) in which you wrap a piece of code you want to execute in a series of forms in the pattern (name (...args...) . body). For instance, here's some code from the SVG thread:

  (defop circles req
      (svgpage
        (svg (svg width 500 height 500)
          (repeat 20
            (svg (circle cx (rand-range 100 400)
                         cy (rand-range 100 400)
                         r (rand-range 10 70)
                         fill (rand-hex-color)
                         opacity (num (/ (rand-range 4 8) 10))))))))
It should be possible to linearize these and reduce the number of parentheses at the end (and also the amount of syntactic nesting, although not logical nesting). This might work:

  (defop circles req
    (block
      (svgpage)
      (svg (width 500 height 500))
      (repeat 20)
      (svg)
      (circle cx (rand-range 100 400)
              cy (rand-range 100 400)
              r (rand-range 10 70)
              fill (rand-hex-color)
              opacity (num (/ (rand-range 4 8) 10)))))
Block is a form that takes a series of lists, and combines them so that the end of each list gets a sublist which is the rest of the block (also combined with block), and the last list is unmodified.

I would appreciate any comments on the idea. The syntax isn't final, but I think it's pretty clear what I intend. (And if you have an idea for how to do it better, by all means, please tell me : ] )



4 points by kens 5850 days ago | link

I've been thinking too that there must be a better way to deal with all these "(name (...args...) . body)" macros, although I've been approaching it from a different direction. The "macro for decorating body" design pattern is happening so often, it makes me feel something is wrong.

For instance, in srv.arc, 15 out of 16 macros are of the form "... ,@body ..." Macros seem a heavyweight way of doing this sort of #define-style replacement. Another thing that bothers me is that it's impossible to tell without examining the macro what gets evaluated immediately, what gets evaluated as part of the macro expansion, and what gets evaluated at some later time, for example in:

  (w/link-if (prn 1) (prn 2) (prn 3) (prn 4))
There's no way to tell when these four expressions are evaluated without digging through the w/link-if code. There's also no way to tell which expressions are logically grouped together.

Arc has several innovative new syntaxes. It seems as if there must be some better new syntax for handling body macros, and Arc should provide it. Surely Lisp's syntax isn't the 100-year answer.

-----

1 point by almkglor 5850 days ago | link

as far as I can tell, `(insert your macro code here ,@body) is the generalized solution form of that design pattern. Just template the code: insert this part here, insert that part there.

The nice thing about templating is that you reasonably arbitrarily insert various bits of code:

  (mac do body
    (w/uniq rv
      (let (desc . body) (if (isa (car body) 'string) body (cons (uniq) body))
        `(let ,rv nil
            (profile-enter ,desc)
            (after (= ,rv ((fn () ,@body)))
                   (profile-leave ,desc)
                   ,rv)))))
Of course, Arc does indeed try to provide specific, shorter solutions for the most common cases, such as single-argument functions ([... _ ... ] syntax), single-variable 'let (let syntax), and anaphoric macros.

Perhaps we can define mac-sym?

  (mac-sym foob
    '(insert your code here))

  (foob
    niaw
    woof
    arf)
  ==
  (insert your code here
    niaw
    woof
    arf)

  (mac mac-sym (name . body)
    (w/uniq m-body
      `(mac ,name .m-body
          (join (do ,@body) ,m-body))))
Or is what is bothering you something else entirely?

-----

5 points by almkglor 5850 days ago | link

Or better to use : syntax

  (defop circles req
    (svpage:svg (svg width 500 height 500)
      (repeat 20
        (svg:circle cx (read-range 100 400)
                    cy (read-range 100 400)
                    r (read-range 10 70)
                    fill (rand-hex-color)
                    opacity (num:/ (rand-range 4 8) 10)))))

-----

1 point by nlavine 5850 days ago | link

That works, but it only eliminates half as much nesting. Would it work for more with first-class macros? (I'm just going to expand it into a compose call because I don't want to make it all one symbol)

  (defop circles req
    (compose
      svpage [svg (svg width 500 height 500) _]
      [repeat 20 _] svg
      (circle cs (read-range 100 400)
              cy (read-range 100 400)
              r (read-range 10 70)
              fill (rand-hex-color)
              opacity (num:/ (rand-range 4 8) 10))))

-----

2 points by almkglor 5850 days ago | link

That won't work. You see, foo:bar in function position is handled specially:

  (idfn foo:bar)
  => (idfn (compose foo bar))

  (foo:bar idfn)
  => (foo (bar idfn))
Compose does not work with macros.

But really, what improvement in readability do you get with the 'block style? The details of 'repeat etc. aren't very special-looking anymore.

-----

3 points by eds 5849 days ago | link

> Compose does not work with macros.

Exactly what do mean? Macros seem to work in functional position at least.

  arc> (and:or nil t)
  t
> But really, what improvement in readability do you get with the 'block style?

I agree with you on this. 'block doesn't really remove parens, it just moves them around and removes the nested structure. According to pg's Ansi Common Lisp, "Lisp programmers read and write code by indentation, not by parentheses." I find nested code to be more readable than flat code for exactly that reason, even if the parens stack up at the end of the block.

-----

1 point by almkglor 5849 days ago | link

This is because the expression:

  (and:or nil t)
expands to:

  (and (or nil t))
But the expression:

  (idfn and:or)
Expands to:

  (idfn (compose and or))
If you read my comment well, you'll see that I specifically mentioned that:

> You see, foo:bar in function position is handled specially:

-----

2 points by absz 5849 days ago | link

Actually, I believe what's happening is that

  (and:or nil t)
becomes

  ((compose and or) nil t)
, which is then expanded to

  (and (or nil t))
by ac.scm. Observe:

  arc> ((compose and or) nil t)
  t

-----

1 point by absz 5849 days ago | link

As almkglor observed, compose is handled specially in functional position, being expanded during compilation. Observe:

  arc> (and:or '(nil t))
  (nil t)
  arc> (apply and:or '(nil t))
  Error: "Function call on inappropriate object #3(tagged mac #<procedure>) (nil t)"

-----

2 points by eds 5849 days ago | link

Which is exactly what I meant by "macros seem to work in functional position." But amkglor's original statement "compose does not work with macros" does not take into account the special treatment of 'compose in functional position, which is why I was confused.

And if I am not mistaken, first class macros, once implemented, could be used in other situations like your example above.

-----

3 points by absz 5849 days ago | link

Looking at alkmglor's comment, it appears to indicate that a literal compose doesn't work; when it's optimized out, it works fine. In other words, everyone is vehemently agreeing with each other ;)

And of course, first class macros could do this. But we don't have those yet…

-----

1 point by nlavine 5849 days ago | link

I find it more readable personally, and definitely easier to write, if there are fewer parentheses at the end of the block. Also, the lack of indentation keeps things lined up nicely, without spilling them off the right edge of the screen.

But really, the block macro defines context (in fact, I considered calling it "context"). That is the semantic meaning. It gives you a list of expressions that define the context for the rest of the list.

I personally find it easier to read, but I am curious about whether other people do, and why or why not. You said the details of 'repeat don't look special anymore. Do you think they should?

-----

2 points by absz 5851 days ago | link

That's an interesting idea, which would work nicely in some cases. (For instance, I like how (svgpage) looks, but not how (repeat 20) looks.) Here's an implementation (lightly tested):

  (mac block body
    (if (or (no body) (~cdr body))
      (carif body)
      (join (car body) (list `(block ,@(cdr body))))))

-----

2 points by raymyers 5850 days ago | link

On a related note, try out Paredit mode in Emacs and see if the nesting-level still bothers you.

-----

1 point by eds 5850 days ago | link

Just tried Paredit and I really like it. Thanks!

-----

1 point by wfarr 5851 days ago | link

I think that it kind of defeats the fundamental nature of Lisp, and introduces overhead. =/

-----