Error monad in Python - did I do it right?

Go To StackoverFlow.com

4

Questions:

  • Is this a monad?
  • does this demonstrate a reasonable understanding of error monad?
  • what am I missing?
  • what else can I do with this code to flex monads more?
  • i'm confused as to the relation of success/fail to "return"/"result"/"lift" (i think these are all the same concepts).
  • how can we make the problem more complex, such that monads help us solve our pain points? monads help here because we abstracted away the if result != None plumbing, what other types of plumbing might I want to abstract, and how do monads (or 'monad combinators') help this pain?

I'm a bit underwhelmed.

# helpers for returning error codes
def success(x): return (True, x)
def fail(x): return (False, x)

# bind knows how to unwrap the return value and pass it to
# the next function
def bind(mv, mf):
    succeeded = mv[0]
    value = mv[1]

    if (succeeded): return mf(value)
    else: return mv

def lift(val): return success(val)

def userid_from_name(person_name):
    if person_name == "Irek": return success(1)
    elif person_name == "John": return success(2)
    elif person_name == "Alex": return success(3)
    elif person_name == "Nick": return success(1)
    else: return fail("No account associated with name '%s'" % person_name)

def balance_from_userid(userid):
    if userid == 1: return success(1000000)
    elif userid == 2: return success(75000)
    else: return fail("No balance associated with account #%s" % userid)

def balance_qualifies_for_loan(balance):
    if balance > 200000: return success(balance)
    else: return fail("Insufficient funds for loan, current balance is %s" % balance)

def name_qualifies_for_loan(person_name):
    "note pattern of lift-bind-bind-bind, we can abstract further with macros"
    mName =    lift(person_name)
    mUserid =  bind(mName, userid_from_name)
    mBalance = bind(mUserid, balance_from_userid)
    mLoan =    bind(mBalance, balance_qualifies_for_loan)

    return mLoan

for person_name in ["Irek", "John", "Alex", "Nick", "Fake"]:
    qualified = name_qualifies_for_loan(person_name)
    print "%s: %s" % (person_name, qualified)

output:

Irek: (True, 1000000)
John: (False, 'Insufficient funds for loan, current balance is 75000')
Alex: (False, 'No balance associated with account #3')
Nick: (True, 1000000)
Fake: (False, "No account associated with name 'Fake'")
2012-04-04 01:36
by Dustin Getz
Very nice question. I think the code is okay as it is, but it could be spiced up a lot by overloading the >> operator and using lambdas. A totally different approach is the one from here, that guy uses decorators and yield to make this almost look right : - Niklas B. 2012-04-04 02:02
I restructured that code slightly to make it more readable. Hope you don't mind (and if you do, please roll it back : - Niklas B. 2012-04-04 02:07


2

Is this a monad? See the monad laws:

All instances of the Monad class should obey:

  1. "Left identity": return a >>= f ≡ f a
  2. "Right identity": m >>= return ≡ m
  3. "Associativity": (m >>= f) >>= g ≡ m >>= (\x -> f x >>= g)

(return means success, >>= means bind)

  1. Left identity. In your implementation, this could be:

    bind(success(x), balance_qualifies_for_loan) == balance_qualifies_for_loan(x) 
    

    where x is some value and f is a monadic function.

  2. Right identity. Again, this could be:

    bind(m, success) == m
    

    where m is a monadic value.

  3. Associativity. This could be:

    bind(bind(m,  userid_from_name), balance_from_userid)) ==
      bind(m, lambda x: bind(userid_from_name(x), balance_from_userid))
    

All of these could be written as unit tests to quickly check that these properties hold for many input values.

What is missing?

  • each monad needs a different implementation of success and bind. Putting these into an interface would allow you to write code generic over all implemented monads.
  • based on the Haskell approach, you might want to implement some general monad combinators, such as >>, sequence and mapM. These make monads very convenient to use.
2012-04-04 02:12
by Matt Fenwick
can you extend my problem such that monad combinators help me solve it? I guess I'm trying to understand what sort of pain points one might have that monads help solve - Dustin Getz 2012-04-04 02:17