-
Notifications
You must be signed in to change notification settings - Fork 4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[enh] Named arguments for macros #13
Comments
I don't see a compelling argument here.. just "would be really nice"? |
The argument is that currently, it's not possible to do certain things - such as, in the use case above, define a λ macro that allows default values for its arguments, or allows The point of this macro is to reduce the amount of typing - not for The example is perhaps a bit silly in that I have no idea if I'll ever use this particular macro in production code - it's so unpythonic that it borders on the limit of good taste. At least I won't use it if there is no way to give defaults to arguments, and it doesn't support I can try to find a better example where the feature is needed; this is just where I first noticed the missing feature, so I thought I'd open a ticket for discussion before I forget. |
I don't know if it makes the use case any more compelling, but Technologicat/unpythonic@b51337b makes To become really useful, I think I could take a look at what it would take to support named args in MacroPy (to support default values for args in |
Here, I made a first cut of this: Technologicat@653b2d2 At least all tests still pass, so I probably didn't break much :3 Here's also an updated λ that uses the new mechanism, for declaring default values: Technologicat/unpythonic@4e2a28c How it works:
As for Anyway, named args for macros are now here - thoughts? [edit] clarify why OrderedDict; mention data types of key and value. |
Re-checking PEP 448, the proposed first-cut solution does need a small revisit after upgrading to Python 3.5, because multiple Multiple Supporting multiple |
Ah, well, second cut: Technologicat@ddd9d75 Ditched the Now, thoughts? :) |
Thanks Juha, but your solution and in general all the situation leaves me more perplexed... I've opened the door to calling the macro with any positional argument or keyword with I would rather prefer passing those parameters and keywords as real python objects, not some AST trees, but when expansion happens there nothing running yet so this would be working only for parameters and keywords bound to literals or pure expressions.... I need to think over it and to see some real example... isolated. Anyway please open a PR with your code. I mean, move your commits to another branch (one per PR) and open a PR from it or your code will not be commentable. Please post here some example of your lambda macro, with comments so that I can understand what it's meant to do without reading a ton of code |
Thanks for the heads-up, I'll make my code commentable and follow up with a PR for discussion. IMHO, args as an AST is a feature, not a bug; as you said, it's before run-time, so nothing exists yet. Leaving it to each macro to decide what to do with the input ASTs sounds to me it's exactly within the job description of a macro. Why some form of @macros.expr
def let(tree, args, **kw): # args: sequence of ast.Tuple: (k1, v1), (k2, v2), ..., (kn, vn)
names = [k.id for k, _ in (a.elts for a in args)]
values = [v for _, v in (a.elts for a in args)]
lam = q[lambda: ast_literal[tree]]
lam.args.args = [arg(arg=x) for x in names]
return q[ast_literal[lam](ast_literal[values])]
@macros.expr
def letseq(tree, args, **kw):
if not args:
return tree
first, *rest = args
return let.transform(letseq.transform(tree, *rest), first) Note let((x, 1),
(y, 2))[
print(x + y)]
letseq((x, 1),
(x, x+1))[
print(x)] The new identifiers are declared as bare names - being able to do this relies on the fact that the input is an AST. Sure, we could place the bindings at the beginning of the let[((x, 1),
(y, 2)),
print(x + y)] but a separate bindings section looks more readable. Why some form of named args - it would let us do this: let(x=1,
y=2)[
print(x + y)] which looks more pythonic. It also allows neat new stuff like args with default values in Finally, this particular Whether an In the context of the new I think some isolation is needed; it is perfectly valid to define a |
Since I promised "neat new stuff", here's also an example on λ (all safeties stripped): @macros.expr
def λ(tree, args, kwargs, **kw): # <-- requires the kwargs hack
withdefault_names = [k.arg for k in kwargs]
defaults = [k.value for k in kwargs]
names = [k.id for k in args] + withdefault_names
newtree = do.transform(tree)
lam = q[lambda: ast_literal[newtree]]
lam.args.args = [arg(arg=x) for x in names]
lam.args.defaults = defaults # for the last n args
return lam
@macros.expr
def do(tree, **kw):
... # beside the point; see unpythonic.syntax Usage: echo = λ(myarg="hello")[print(myarg),
myarg]
assert echo() == "hello"
assert echo("hi") == "hi"
count = let((x, 0))[
λ()[x << x + 1,
x]]
assert count() == 1
assert count() == 2
myadd = λ(x, y)[print("myadding", x, y),
localdef(tmp << x + y),
print("result is", tmp),
tmp]
assert myadd(2, 3) == 5 The essential point is, No [edit] The |
I just obsoleted my λ; this is much more pythonic, not to mention less brittle: @macros.block
def multilambda(tree, **kw):
@Walker
def transform(tree, *, stop, **kw):
if type(tree) is not Lambda or type(tree.body) is not List:
return tree
bodys = Tuple(elts=tree.body.elts, ctx=Load())
bodys = copy_location(bodys, tree)
stop() # don't recurse over what do[] does
bodys = transform.recurse(bodys) # but recurse over user code
tree.body = do.transform(bodys)
return tree
yield transform.recurse(tree) Usage: with multilambda:
echo = lambda x: [print(x), x]
assert echo("hi there") == "hi there"
count = let((x, 0))[
lambda: [x << x + 1,
x]]
assert count() == 1
assert count() == 2
t = lambda: [[1, 2]]
assert t() == [1, 2] The pythonic |
sorry -- this has little to do with the interesting discussion of late, but i wonder about the contrived code example in the first comment i don't understand how this works in 2 ways, even in custom
myadd = λ(x, y)[print(x, y), x + y]
but as well Perhaps And the import also enables some "lazy-loading" feature of the Python interpreter so that |
Cat: it's a macro thing. :) Roughly speaking, a macro intercepts and transforms code before the rest of the interpreter even sees it. It just needs to be valid syntactically, so that Python's parser can convert the source code text to an AST. MacroPy then hands over (relevant parts of) this AST to macros, to be transformed into a new AST. Normal run-time interpretation starts only after all macros have "run" (been expanded). This gives some flexibility normal code doesn't have. The λ is a macro; it looks like a function call, but it's subtly different. The Both args and body are sent to the same "call" of the macro. Hence, Now, unpythonic does a bit of magic - the body of λ gets wrapped with an The "do" macro, which is what the λ macro actually inserts, then makes this a bit easier to use, by taking the code entered by the user, and wrapping each item in a lambda - automatically - so that execution is delayed until the underlying Hope this helps :) [edit] fix text formatting |
Yes, it helps very much! I sort of thought Python tries to resolve names at parse time and complain at runtime, but this is all very interesting to learn :) |
Cat: AFAIK, Python basically resolves everything at runtime. Only reserved words such as I've sometimes tripped over this myself, when writing a context manager, declaring def __exit__(self, type, value, traceback):
... and then wondered why a call to the built-in |
On some occasions, being able to pass named arguments to macros would be useful.
Use case, related to the multilambda macro in unpythonic (rackety lambda with implicit begin, for Python):
(Usage, implementation.)
For the same use case,
*args
and**kwargs
support would be really nice. :)Thoughts?
[edit] update link.
[edit2] these links are now obsolete; the silly λ macro has been removed.
The text was updated successfully, but these errors were encountered: