if result != Noneplumbing, 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 value = mv 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)
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'")
>>operator and using lambdas. A totally different approach is the one from here, that guy uses decorators and
yieldto make this almost look right : - Niklas B. 2012-04-04 02:02
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)
x is some value and
f is a monadic function.
Right identity. Again, this could be:
bind(m, success) == m
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?
bind. Putting these into an interface would allow you to write code generic over all implemented monads.
mapM. These make monads very convenient to use.