[Python-ideas] History on proposals for Macros?

Andrew Barnert abarnert at yahoo.com
Sat Mar 28 20:50:09 CET 2015


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.

Python already has a few things you can do at runtime that affect subsequent compilation, like __future__ statements; this isn't impossible.

Of course that doesn't mean it's a good idea. It would require some pretty significant changes that may not be immediately obvious (e.g., the whole .pyc mechanism no longer works as-is if you can import macros from other modules, which isn't an issue for __future__ statements...). And debugging could be a nightmare--you now have to know what names are defined at call time, and also what names were defined at definition time, to trace through a function. And so on.

> I really like the idea of delaying the evaluation of certain expressions 
> until a time of the caller's choosing, but I don't see how to make that 
> work automatically. Of course we can do it manually by wrapping the 
> expression in a function,

That doesn't work for all uses of macros--a macro can swap two variables, or break or return, etc., and a higher-order function whose arguments are delayed by wrapping them in a function can't do that. But it does work for _many_ uses of macros, like this one.

And if you look at languages with light lambda syntax, a lot of things that you'd naturally write as a macro in Lisp, you instead naturally write as a plain higher-order function, and the result is often more readable, not less. (Explicit is better than implicit, but you sometimes don't realize it if the explicitness forces you to write more boilerplate than actual code...) Consider:

    assertRaises(ZeroDivisionError, :1/0) 

That ":1/0" means the same thing as "lambda: 1/0". And now "wrapping the expression in a function" doesn't seem so bad.

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.





More information about the Python-ideas mailing list