azazel75 / macropy

Macros in Python: quasiquotes, case classes, LINQ and more!
29 stars 4 forks source link

[enh] Quasiquoting calls that take named arguments #15

Closed Technologicat closed 6 years ago

Technologicat commented 6 years ago

In Python, functions often take named arguments. Currently the way to construct, in a macro, a function call that needs to pass arguments by name is to do it manually:

from ast import Call, keyword
from mylibrary import f

@macros.expr
def m(tree, **kw):
    kw1 = keyword(arg="hello", value=Str(s="world"))
    kw2 = keyword(arg="answer", value=Num(n=42))
    node = Call(func=hq[f], args=..., keywords=[kw1, kw2])
    ...

If the quasiquote operators q and hq supported named arguments in quasiquoted function calls, this would shorten to a more pythonic:

from mylibrary import f

@macros.expr
def m(tree, **kw):
    node = hq[f(..., hello="world", answer=42)]
    ...

Even though *args and **kwargs handling changed in Python 3.5, the GTS documentation on Call seems to suggest this part was not affected.

A real use case in the forall macro in unpythonic. (Example, implementation.)

(If this is a duplicate, then sorry, and please close; I was almost sure I posted this yesterday, but since it appears not be there now, I'm assuming I forgot to click the post button. It's easy to lose track when opening a bunch of tickets at once.)

Technologicat commented 6 years ago

Results of my own investigation:

This already works just fine:

s = ';'
q[print(1, 2, 3, sep=u[s], end='\n')]  # q[] or hq[]

What we don't have is the ability to interpolate the names of named arguments; but that's something Python's parser doesn't support (SyntaxError: keyword can't be an expression), so it's not something MacroPy could easily fix.

The reason I thought it didn't work was that in the original application where I was using this, I needed to interpolate both sides; this case indeed requires constructing at least a keyword manually. Then, either quote what you can and then splice that in:

k = keyword(arg=compute_name(), value=Str(s="meow"))
c = hq[f(1, 2, 3)]  # --> Call node
c.keywords = [k]

or create the whole Call manually:

k = keyword(arg=compute_name(), value=Str(s="meow"))
c = Call(func=hq[f], args=[Num(n=1), Num(n=2), Num(n=3)], keywords=[k])

So, I'm closing this issue - already working as far as reasonable. :)

Sorry for the noise.