3

how terrible an idea is this? class monad implements the with interface to put things in and out of scope, so i can write a library of generic functions like m_chain who refer to functions unit and bind who can have an implementation put in at runtime. (It doesn't matter what all this code does or if it's a good idea.)

other ideas i tried all revolved around passing around a structure containing unit/bind as an argument or a kwarg, or putting m_chain in a class, implement it in terms of self.unit and self.bind and having derived classes provide them. but it added complexity to the code and syntax and tied unit/bind to the way monads are expressed in python. using scope for this just feels so much nicer.

class monad:
    """Effectively, put the monad definition in lexical scope.
    Can't modify the execution environment `globals()` directly, because
    after globals().clear() you can't do anything.
    """
    def __init__(self, monad):
        self.monad = monad
        self.oldglobals = {}

    def __enter__(self):
        for k in self.monad:
            if k in globals(): self.oldglobals[k]=globals()[k]
            globals()[k]=self.monad[k]

    def __exit__(self, type, value, traceback):
        """careful to distinguish between None and undefined.
        remove the values we added, then restore the old value only
        if it ever existed"""
        for k in self.monad: del globals()[k]
        for k in self.oldglobals: globals()[k]=self.oldglobals[k]


def m_chain(*fns):
    """returns a function of one argument which performs the monadic
    composition of fns"""
    def m_chain_link(chain_expr, step):
        return lambda v: bind(chain_expr(v), step)
    return reduce(m_chain_link, fns, unit)




identity_m = {
    'bind':lambda v,f:f(v),
    'unit':lambda v:v
}

with monad(identity_m):
    assert m_chain(lambda x:2*x, lambda x:2*x)(2) == 8


maybe_m = {
    'bind':lambda v,f:f(v) if v else None,
    'unit':lambda v:v
}

with monad(maybe_m):
    assert m_chain(lambda x:2*x, lambda x:2*x)(2) == 8
    assert m_chain(lambda x:None, lambda x:2*x)(2) == None
Dustin Getz
  • 21,282
  • 15
  • 82
  • 131
  • Seems legit. I'm interested in what some of the answers will be. I dig the effect, letting you swap in and out of variations of a function on demand. If there's a downside I don't see one. I'd probably be careful to provide default definitions of the functions `m_chains` is going to use, or throw a meaningful exception if they're not set, but otherwise I can see some decent applications for something like this. – g.d.d.c Jul 21 '12 at 17:08

1 Answers1

0

I think continuously duckpunching globals is definitely a terrible idea. Relying on globals seems like the antithesis of the functional style you're emulating here.

Why not define m_chain as:

def m_chain(bind, *fns):
    """returns a function of one argument which performs the monadic
    composition of fns"""
    def m_chain_link(chain_expr, step):
        return lambda v: bind(chain_expr(v), step)
    return reduce(m_chain_link, fns, unit)

Then:

identity_m = {
    'bind':lambda v,f:f(v),
    'unit':lambda v:v
}

with monad(identity_m):
    assert m_chain(lambda x:2*x, lambda x:2*x)(2) == 8

Becomes simply:

assert m_chain(lambda v,f:f(v), lambda x:2*x, lambda x:2*x)(2) == 8

Actually passing the function explicitly seems more pythonic and doesn't seem to cause you to lose any flexibility.

stderr
  • 8,567
  • 1
  • 34
  • 50