lihaoyi / macropy

Macros in Python: quasiquotes, case classes, LINQ and more!
3.28k stars 178 forks source link

macro(...) vs macro%... vs macro[...] #45

Closed lihaoyi closed 11 years ago

lihaoyi commented 11 years ago

The original decision to use macro%... was due to:

Although the macro%... syntax still benefits macros such as string interpolation (s%"..."), it seems the vast bulk of our macros would look much better using the macro(...) or macro[...] syntax instead. This is partially because of the high precedence of the % operator, turning basically many macro%... calls into macro%(...) calls anyway:

sql%(x.size for x in db.countries)
require%(value == 10)
trace%(1 + 2 + 3)
f%(_ + _)
q%(1 + 2)
with q as code:
    x = 10
code[0].targets[0].id = n
with match:
    Point(x, y) << my_point
s%"i a {cow}" 
p%"<h1>Hello World</h1>
with peg:
    op = "+" | "-" | "*" | "/"

vs

sql(x.size for x in db.countries)
require(value == 10)
trace(1 + 2 + 3)
f(_ + _)
q(1 + 2)
with q as code:
    x = 10
code[0].targets[0].id = n
with match:
    Point(x, y) << my_point
s("i am {cow}")
p("<h1>Hello World</h1>")
with peg:
    op = "+" | "-" | "*" | "/"

vs

sql[(x.size for x in db.countries)]
require[value == 10]
trace[1 + 2 + 3]
f[_ + _]
q[1 + 2]
with q as code:
    name[n] = 10
match[Point(x, y)] = my_point
s["i am {cow}"]
p["<h1>Hello World</h1>"]
peg[op] = "+" | "-" | "*" | "/"

In addition, using macro[...] means you can place macros on the left hand side of an assignment, which allows for a nice looking shorthand match and peg syntax, rather than having to use with ...: block macros when you really only need a single-statement.

Due to the fact that you can now rename macros when importing them, the downside of a name collision is much less severe: just rename the macro using ...import macros, sql as macro_sql or similar.

We could:

lihaoyi commented 11 years ago

Did this, but also left the old syntax in for string interpolation (s%"...") and pyxl (p%"...") to use. All the other macros now use the new syntax, including the the docs and examples.

https://github.com/lihaoyi/macropy/commit/b6015ce6e2fccc8e8142441781158fc32af491cd

macobo commented 11 years ago

Perhaps is using angular brackets worth considering?

sql[(x.size for x in db.countries)]
require[value == 10]
trace[1 + 2 + 3]
f[_ + _]
q[1 + 2]

Not saying this is necessarily better, but it might help distinguish macros from function calls. If we want to be even more explicit, there's double brackets - q[[1+2]].

lihaoyi commented 11 years ago

That's a possibility; I really believe in macros "blending in" to the surrounding code, which is why I wanted require(blah) to look like the existing assertEquals(blah). For quasiquotes [[double square brackets]] look great, but it doesn't work well for the others.

Also, square bracket's "I'm an array access!" implications may be annoying, when macros generally have more function-like semantics.

On the other hand, square brackets do look really pretty, and probably have an order of magnitude lower chance of collision, and the explicitness (require[blah] is obviously not an array access) is nice. Honestly not sure what the best way to go is, and changing back and forth is annoying (it was ~1hr of going through all the code, examples and docs).

jnhnum1 commented 11 years ago

I think it is important to have macro calls be distinguishable from ordinary function calls, since macros can do weird stuff, which could be really confusing to a programmer if he thinks foo is an ordinary functions. That said, I think macro syntax should closely approximate what the semantics of the macro are, which can differ between macros. For me at least, when I see a function call, my brain automatically recognizes that pattern and tells me that the arguments are evaluated first, then passed to this function. I think square brackets would be a nice reminder that I'm dealing with something different.

One nice thing about using square brackets is that they can be an lvalue (left hand side in an assignment). In particular, you could then unquote names like this: with q: name[foo] = 5

lihaoyi commented 11 years ago

That is a very good point that I did not think of. We can have macros like

match[...] = ...

with q as code:
    u[...] = ...

All of which are impossible in either of the current schemes, and necessitates annoying workarounds:

with patterns:
   ... << ...

with q as code:
   x = ...

code[0].targets = [...]
lihaoyi commented 11 years ago

We will need some additional cases to allow the macro-ed LHS capture the entire assignment statement, but I think it is generally useful enough to be an acceptable addition. The Parser Combinator macros could use it, and in general it would allow us to treat

decorate[x] = ...

macros as "decorators" for individual statements, placing the decoration near the name rather than around the body (...) just like how function/class decorators do it. It's a simple syntactic transform over

x = decorate(...)

but sometimes the difference in readability are significant.

lihaoyi commented 11 years ago

Updated the original issue to include side by side comparisons of all three syntaxes. I think the square brackets looks the best overall, despite being slightly clunky for PINQ (you need nested [()] or [[]]). Being able to unquote the left side of an assignment, and using single-line match[] or peg[] macros is also very nice. I also think forcing everyone to use one style for macros has an consistency advantage over letting people pick and choose different things, so I'm for all expr macros using macro[...] syntax.

lihaoyi commented 11 years ago

Pushed this change out, including all the docs and examples, since nobody disagreed enough to object here.

I think everything looks much nicer now using square brackets =)