• Take original function
  • Transform to AST -> ast.parse . inspect.getsource . inspect.unwrap
    • it's wrapped in do, unwrap it so we can get the original function
    • pull that source code out
    • get to nougatty goodness that is the ast
  • Walk AST w/ custom Transformer
    • What is written: a = yield monadic_function(var1. var2)
    • What we want: monadic_function(var1, var2) >> (lambda a: ...)
    • or yield ignore_this_result() to ignore_this_result >> (lambda _: ... )
    • Do it in reverse?
      • Gradually build up an expression?
      • Begin with Monad.unit(final)
      • Next is: final = yield monadic_func_n(c)
      • then: c = yield monadic_func_c(a, b)
      • then b = yield monadic_func_b(...)
      • then a = yield monadic_func_a(...)
      • finally: any top level assigns: thing = ...
  • Take new AST and compile it
  • Exec it into wrapper namespace (omg why)
  • Return converted function
  • Burn in hell forever.

Proof of Concept of replacing a function's __code__ attribute


In [1]:
import ast
import inspect
from pynads import Just, Nothing, Maybe
from pynads.utils.decorators import kwargs_decorator

#dummy mreturn and do
@kwargs_decorator
def do(f, monad):
    return f

mreturn = lambda x: x

In [2]:
def safe_div(a,b):
    if b == 0:
        return Nothing
    else:
        return Just(a/b)


@do(monad=Maybe)
def safe_div_do(first):
    a = yield safe_div(1, first)
    b = yield safe_div(first, 3)
    c = yield safe_div(b, a)
    mreturn(c)
    
def safe_div_bind(first):
    return safe_div(1, first) >> (lambda a:
           safe_div(first, 3) >> (lambda b:
           safe_div(a, b)     >> (lambda c:
           Maybe.unit(c)                )))

In [3]:
safe_div_do(1)


Out[3]:
<generator object safe_div_do at 0x7fed980bb678>

In [4]:
safe_div_do_ast = ast.parse(inspect.getsource(inspect.unwrap(safe_div_do)))
safe_div_bind_ast = ast.parse(inspect.getsource(safe_div_bind))

# replacement indicator
# note: this applied *after* we parsed the ast
safe_div_do.original = True

In [5]:
safe_div_do_ast.body[0].body = safe_div_bind_ast.body[0].body

In [6]:
f = compile(safe_div_do_ast, '<string>', 'exec')
exec(f, globals(), locals())

In [7]:
safe_div_do(1)


Out[7]:
Just 3.0