Macros in Python
github.com
github.com
[2] https://github.com/natefaubion/adt.js
I love love love the PEG implementation.
#run.py
import macropy.core.macros
import target
#batcher.py
# http://en.wikipedia.org/wiki/Batcher_odd%E2%80%93even_mergesort
def oddeven_merge(lo, hi, r):
step = r * 2
if step < hi - lo:
for x in oddeven_merge(lo, hi, step): yield x
for x in oddeven_merge(lo + r, hi, step): yield x
for i in range(lo + r, hi - r, step): yield (i, i+r)
else:
yield (lo, lo + r)
def oddeven_merge_sort_range(lo, hi):
if (hi - lo) >= 1:
mid = lo + ((hi - lo) // 2)
for x in oddeven_merge_sort_range(lo, mid): yield x
for x in oddeven_merge_sort_range(mid + 1, hi): yield x
for x in oddeven_merge(lo, hi, 1): yield x
def oddeven_merge_sort(n):
return list(oddeven_merge_sort_range(0, n-1))
#target.py
from macro_module import macros, my_expr_macro
import dis
my_expr_macro%(8)
print dis.dis(fun8)
print fun8([2, 4, 3, 5, 6, 1, 7, 8])
#macro_module.py
from macropy.core.macros import Macros
from macropy.core import *
from ast import *
macros = Macros()
import macropy.macros
from batcher import oddeven_merge_sort
@macros.expr
def my_expr_macro(tree):
return parse_stmt("def fun" + str(tree.n) + "(a):\n" +
"\n".join([" if a[{0}] > a[{1}]: tmp = a[{0}]; a[{0}] = a[{1}]; a[{1}] = tmp".format(x,y) for (x,y) in oddeven_merge_sort(tree.n)]) +
"\n return a")
When running, it will spit out the Python bytecode disassembly to show the unrolled sorting loop.I just put up a Detailed Guide:
https://github.com/lihaoyi/macropy#detailed-guide
That goes into more detail of how macros are written, and walks you through writing a simple but useful macro (the quicklambda macro). This should greatly help anyone who wants to try his hand at some metaprogramming =)
[1] Lisp in Python https://github.com/paultag/hy
We think that with some discipline, it is possible to come up with macro transformations which are both useful and understandable by a programmer. This means setting clearly defined semantics for the transformations, which is difficult but doable.
If you think macros are kinda crazy (well, they are!) you should look at the implementation of such pythonic constructs like `namedtuple`, the `ast` module and the new Enum library coming out!
We're used to namedtuples doing it like this now, but if namedtuples didn't exist, "fields as a list of strings with spaces inside, or maybe a list of strings" would definitely not make it past code review and probably get me yelled at by my future colleagues.
One thing case classes let you do is implement functionality externally to a class using pattern-matching functions. When you do this, your case class definition will consist of nothing but empty class definitions:
@case
class List:
class Nil(): pass
class Cons(x, xs): pass
In any case, Python's enums will use fully qualified names, so we may change our current system for compatibility and consistency.Your macropy is very interesting, but please do not use this argument. "Saving some typing" is the least interesting side-effect of some macros.
To clarify my point: in most of the industry, we all fight hard to garden big codebases with intricate dependency problems. I am convinced that code complexity management is very far from a solved problem, and I hope advanced tools like macros can help there.
But "saving a few keystrokes" is very often at the heart of the stupides issues we have with code complexity.
Two examples:
+ "from x import " <-- this saves a few typing, but it become extremely annoying as soon as your code get more than 10 modules.
+ template.render(tpl_file, locals()) <-- this feeds the template space with shit and makes it much harder to trace data between templates and models. I daily revile the guy who typed the "*locals()" in the Mako doc.
I've also gotten a basic implementation working in the CPython REPL, which requires you to run a function `macro_repl()` before you can use them. A Macro powered REPL would be super cool!
PS C:\Dropbox\Workspace\6.945\Project> python
Python 2.7 (r27:82525, Jul 4 2010, 07:43:08) [MSC v.1500 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import macropy.core.macros
0=[]=====> MacroPy Enabled <=====[]=0
>>> from macropy.macros2.tracing import macros, trace
>>> trace%[x*2 for x in range(3)]
range(3) -> [0, 1, 2]
(x * 2) -> 0
(x * 2) -> 2
(x * 2) -> 4
[(x * 2) for x in range(3)] -> [0, 2, 4]
[0, 2, 4]
It's somewhat hacky, but it seems to work pretty well. I went through the examples and most of them work perfectly in the REPL, except for those whose have blank-lines-in-class-def confuse ithttps://github.com/lihaoyi/macropy/blob/master/macropy/core/...
Those already familiar with the imp mechanism and AST module could probably infer it from the description.
An absolutely beautiful facet of Python's nature is that with relatively little background, it's quite straightforward to know how all sorts of 'magic's and hacks work at first glance. :)
Child classes have to be nested classes of the parent doesn't sit right with me. Also, wouldn't it be better if these classes inherited object in some way?
For the record, I liked everything else that it mentioned. Just the inheritance bit was something I didn't like.
Honestly, if I have classes that really do need to inherit from each other, I probably won't be using @case to do it anyways. I see this being extremely useful for the many structures that I need in my application (that may require a few convenience methods) more than anything.
The following code:
with patterns:
Foo(x, Bar(3, z)) << Foo(4, Bar(3, 8))
The constructor after the << will be matched with the part before <<. It will only match if the first argument of "Bar" is 3. If that's the case, x and z will be bound to 4 and 8.Does this help? :)
What's the purpose of only binding x and z if the first argument of Bar matches? What if it doesn't match? Is an exception thrown? Are x,z just None?
[Note: I'm talking about the example, not the @case decorator, which does seem useful]
Edit:
After reading the library examples, the explanation above is not entirely clear:
with patterns:
Foo(x, Bar(3, z)) << Foo(4, Bar(3, 8))
here's the rest of the example: print x # 4
print z # 8
I was thinking that somehow a new Foo instance was created only when the pattern matched. (Oh, and an exception is thrown when the match fails)The purposes of pattern matching is to replace code that looks like this:
if (isinstance(tree, BinOp)
and type(tree.left) is Name
and type(tree.op) is Mod
and tree.left.id in module.expr_registry):
...
with code that looks like this if BinOp(Name(id), Mod(), body) << tree
and id in module.expr_registry:
...
Which looks much nicer, and more clearly says what you want: that `tree` "looks like" a particular shape.EDIT: Here's another example. Turning this:
if ((isinstance(tree, ClassDef) or isinstance(tree, FunctionDef))
and len(tree.decorator_list) == 1
and tree.decorator_list[0]
and type(tree.decorator_list[0]) is Name
and tree.decorator_list[0].id in module.decorator_registry):
...
into: if ((isinstance(tree, ClassDef) or isinstance(tree, FunctionDef))
and [Name(id)] << tree.decorator_list
and id in module.decorator_registry):
...
Doesn't quite work yet, but we're getting there myfoo = Foo(4, Bar(3, 8))
with patterns:
Foo(x, Bar(3, z)) << myfoo
So in this case, the assertion will only pass if the Foo contains a Bar in the second slot, and that Bar contains a 3 in its first slot, while also binding the 4 and 8 to their own names, x and z respectively.Is that a macro created by this library or a feature of the library?
Edit: I did some reading and it looks to be a feature macro of the library.
> MacroPy provides a mechanism for user-defined functions (macros) to perform transformations on the abstract syntax tree(AST) of Python code at module import time
Basically it rewrites the code before it's compiled, so no, x and y don't need to exist (compilation hasn't finished when the macro runs)
@blah
class Lol:
pass
is equivalent to: class Lol:
pass
blah(Lol)
What am I missing?Import hooks.
class Lol:
pass
Lol = blah(Lol)def __iter__(self): current = self
while len(current) > 0:
yield current.head
current = current.tail"The Wonderful Thing About A Dancing Bear Is Not How Well He Dances, But That He Dances At All"
We'll get to teaching him to dance well later!
I wish you the best for your presentation for the Mit class, it seems interesting but perhaps it should have a link to some standard techniques like machine learning.
I am curious about the small projects, will they be available in the page of the course?
still has some issues with line numbers (in tracebacks for example) in files where there is macro expansion.
Although that level of fragmentation you do talk about is plausible - I'd find it simultaneously disheartening and worth a bit of a chuckle. (I'm reminded of all of the concurrency abstractions CL seems "capable of" hosting with minimal friction - but none with definite thrust.)
That's what a lot of other people are using.
It's not that we don't like Python3; their ASTs are considerably simpler (e.g. no print statement), they have importlib, and a bunch of other nice things. It's just that the world we interact with mostly uses Python2, so we just follow along.