lihaoyi / macropy

Macros in Python: quasiquotes, case classes, LINQ and more!
3.28k stars 178 forks source link

Inline/Optimization Macros - Take 2 #60

Open Varriount opened 11 years ago

Varriount commented 11 years ago

So after reading the reasons why my first proposal of a single Optimization Macro parsing the entire file was unfeasible, I got to thinking of better (and possibly more pythonic) ways of implementing inlined function calls.

Instead of a single macro, inlining a function would require two macros - one to register a target function as inline-able, and another to actually inline the registered function into a call site. Proposed usage:

@inlineable
def foo(x, y):
    return x+y

def bar():
    for number in range(0,1000):
        inline(result = foo(number, number+1))
        print(result)

Or

@inline_all
def bar():
    for number in range(0,1000):
        result = foo(number, number+1)
        print(result)

Alternatives to using the the "inline" macro as a function is to use it as a decorator, and have it attempt to scan and inline every registered function call within the body.

I believe that, with some usage limitations, this kind of optimization is quite possible. The only difficulties lie in restructuring function bodies so that they can be safely inlined. Argument handling is also a bit tricky, but I believe that clever use of python's symtable module, along with comparison of the function's argument ast should solve that.

lihaoyi commented 11 years ago

Seems perfectly doable; With the recently developed (but still undocumented) @Scoped walker, I do not think that hygienically renaming the variables within function bodies before inlining will prove difficult.

Possible caveat: you're probably going to need separate cases for inlined expressions and inlined statements, due to the fact that in python you cannot place statements inside expressions. That would cause problems inlining e.g. a multi-statement function as an expression.

Other than that, go ahead, look forward to see what you come up with =)

lihaoyi commented 11 years ago

One possible approach for single-expression-functions would be to make the @inlineable decorator a macro, that will capture the contents of the function and replace it with a macro function which registers itself and performs the inlining. i.e.

@inline
def func(a, b):
    return a * a + b * b # only works for single expression functions

expands into

@macro.expr
def func(tree, all_trees):
    return q[(lambda a, b: a * a + b * b)(ast[all_trees[0]], ast[all_trees[1]])]

which is then used via

print func[1 + 1, 2 + 2] # 9

This can be done pretty straightforwardly, and will "inline" the functions body, but I'm not sure if it will actually make anything any faster. We still need the lambda expression in order to perform variable binding, since we can't inline local variable assignments in expressions otherwise, and that probably will undo the performance benefit from inlining in the first place.

gordianknotC commented 9 years ago

How about this

# mark do_something and do_anotherthing as template and store it's code body.
with template:
    def do_something(x,y):
        print "do something...."

    def do_anotherthing(x,y): pass

# to use do_something as inline function just decorating inlineable ontop templateTester
@inlineable
def templateTester():
    do_something(1,2)

#to use do_something as normal function
def normalFunctionTester():
    do_something(1,2)

Why called template? Because I first learned this concept from Nimrod template

Another interesting and doable things to implement is "do notation"(borrowed from Nimrod do notation)

instead of writing

def walkWhile(condition, do):
    if condition: do(x,y)

def do_func(x,y):
    do something here.....

walkWhile(condition, do_func)

do notation would be more convenient

def walkWhile(condition, do):
    if condition: do(x,y)

walkWhile(condition) do(x,y):
    do something here.....

But due to semantic limitation we can't use walkWhile(condition) do: