[Python-ideas] History on proposals for Macros?
Steven D'Aprano
steve at pearwood.info
Sun Mar 29 03:51:44 CEST 2015
On Sat, Mar 28, 2015 at 12:50:09PM -0700, Andrew Barnert wrote:
> On Mar 28, 2015, at 10:26, Steven D'Aprano <steve at pearwood.info> wrote:
> >
> >> On Sat, Mar 28, 2015 at 09:53:48AM -0700, Matthew Rocklin wrote:
> >> [...]
> >> The goal is to create things that look like functions but have access to
> >> the expression that was passed in.
> >
> >> assertRaises(ZeroDivisionError, 1/0) # Evaluate the rhs 1/0 within
> >> assertRaises function, not before
> >
> >> Generally one constructs something that looks like a function but, rather
> >> than receiving a pre-evaluated input, receives a syntax tree along with the
> >> associated context. This allows that function-like-thing to manipulate the
> >> expression and to control the context in which the evaluation occurs.
> >
> > How will the Python compiler determine that assertRaises should receive
> > the syntax tree rather than the evaluated 1/0 expression (which of
> > course will raise)? The information that assertRaises is a "macro" is
> > not available at compile time.
>
> Well, it _could_ be available. At the time you're compiling a scope (a
> function, class, or top-level module code), if it uses an identifier
> that's the name of a macro in scope, the compiler expands the macro
> instead of compiling in a function call.
Perhaps I trimmed out too much of Matthew's comment, but he did say he
isn't talking about C-style preprocessor macros, so I think that if you
are imagining "expanding the macro" C-style, you're barking up the wrong
tree. From his description, I don't think Lisp macros are quite the
right description either. (As always, I welcome correction if I'm the
one who is mistaken.)
C macros are more or less equivalent to a source-code rewriter: if you
define a macro for "foo", whenever the compiler sees a token "foo", it
replaces it with the body of the macro. More or less.
Lisp macros are different, and more powerful:
http://c2.com/cgi/wiki?LispMacro
http://cl-cookbook.sourceforge.net/macros.html
but I don't think that's what Matthew wants either. The critical phrase
is, I think:
"one constructs something that looks like a function but, rather than
receiving a pre-evaluated input, receives a syntax tree along with the
associated context"
which I interpret in this way:
Suppose we have this chunk of code creating then using a macro:
macro mymacro(expr):
# process expr somehow
mymacro(x + 1)
That is *roughly* equivalent (ignoring the part about context) to what
we can do today:
def myfunction(expr):
assert isinstance(expr, ast.Expression)
# process expr somehow
tree = ast.parse('x + 1', mode='eval')
myfunction(tree)
The critical difference being that instead of the author writing code to
manually generate the syntax tree from a string at runtime, the compiler
automatically generates the tree from the source code at compile time.
This is why I think that it can't be done by Python. What should the
compiler do here?
callables = [myfunction, mymacro]
random.shuffle(callables)
for f in callables:
f(x + 1)
If that strikes you as too artificial, how about a simpler case?
from mymodule import name
name(x + 1)
If `name` will refer to a function at runtime, the compiler needs to
generate code which evaluates x+1 and passes the result to `name`; but
if `name` will refer to a macro, the compiler needs to generate an ast
(plus context) and pass it without evaluating it. To do that, it needs
to know at compile-time which objects are functions and which are
macros, but that is not available until runtime.
But we might be able to rescue this proposal by dropping the requirement
that the compiler knows when to pass the syntax tree and when to
evaluate it. Suppose instead we had a lightweight syntax for generating
the AST plus grabbing the current context:
x = 23
spam(x + 1, !(x+1)) # macro syntax !( ... )
Now the programmer is responsible for deciding when to use an AST and
when to evaluate it, not the compiler, and "macros" become regular
functions which just happen to expect an AST as their argument.
[...]
> If such a light-lambda syntax reduced the desire for macros down to
> the point where it could be ignored, and if that desire weren't
> _already_ low enough that it can be ignored, it would be worth adding.
> I think the second "if" is where it fails, not the first, but I could
> be wrong.
I presume that Matthew wants the opportunity to post-process the AST,
not merely evaluate it. If all you want is to wrap some code an an
environment in a bundle for later evaluation, you are right, a function
will do the job. But it's hard to manipulate byte code, hence the desire
for a syntax tree.
--
Steve
More information about the Python-ideas
mailing list