Questions:
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'")
Is this a monad? See the monad laws:
All instances of the Monad class should obey:
- "Left identity": return a >>= f ≡ f a
- "Right identity": m >>= return ≡ m
- "Associativity": (m >>= f) >>= g ≡ m >>= (\x -> f x >>= g)
(return means success, >>= means bind)
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.
Right identity. Again, this could be:
bind(m, success) == m
where m is a monadic value.
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?
success and bind. Putting these into an interface would allow you to write code generic over all implemented monads.>>, sequence and mapM. These make monads very convenient to use.
>>operator and using lambdas. A totally different approach is the one from here, that guy uses decorators andyieldto make this almost look right : - Niklas B. 2012-04-04 02:02