I’m trying to compose seq-m and error-m to do list comprehensions on things that can return errors. My output has unexpected types, though other than that it does actually seem to be sensible. i’ve exploded my code below, but here is a working gist as well.
here is my monadic business logic
def get_loan(name):
m_qualified_amounts = (
bind(get_banks(name), lambda bank:
bind(get_accounts(bank, name), lambda account:
bind(get_balance(bank, account), lambda balance:
bind(get_qualified_amount(balance), lambda qualified_amount:
unit(qualified_amount))))))
return m_qualified_amounts
names = ["Irek", "John", "Alex", "Fred"]
for name, loans in zip(names, map(get_loan, names)):
print "%s: %s" % (name, loans)
output
Irek: [None, 'Insufficient funds for loan, current balance is 35000', None, 'Insufficient funds for loan, current balance is 70000', None, 'Unable to get balance due to technical issue for Wells Fargo: 3']
John: [None, 'Insufficient funds for loan, current balance is 140000']
Alex: [[245000], None, [280000], None]
Fred: (None, 'No bank associated with name Fred')
i expect to see lists of tuples – the list is the result of the list comprehension, and each item in the final list should be a value in error-monad (value, error tuple). Its exactly as if one-too-many levels of nesting were removed by seq_bind.
here is my definition of the monads, which if its not correct, its very close because both monads work in isolation, just not combined.
def success(val): return val, None
def error(why): return None, why
def get_value(m_val): return m_val[0]
def get_error(m_val): return m_val[1]
# error monad
def error_unit(x): return success(x)
def error_bind(mval, mf):
assert isinstance(mval, tuple)
error = get_error(mval)
if error: return mval
else: return mf(get_value(mval))
def flatten(listOfLists):
"Flatten one level of nesting"
return [x for sublist in listOfLists for x in sublist]
# sequence monad
def seq_unit(x): return [x]
def seq_bind(mval, mf):
assert isinstance(mval, list)
return flatten(map(mf, mval))
# combined monad !!
def unit(x): return error_unit(seq_unit(x))
def bind(m_error_val, mf):
return error_bind(m_error_val, lambda m_seq_val: seq_bind(m_seq_val, mf))
monadic API
def get_banks(name):
if name == "Irek": return success(["Bank of America", "Wells Fargo"])
elif name == "John": return success(["PNC Bank"])
elif name == "Alex": return success(["TD Bank"])
else: return error("No bank associated with name %s" % name)
def get_accounts(bank, name):
if name == "Irek" and bank == "Bank of America": return success([1, 2])
elif name == "Irek" and bank == "Wells Fargo": return success([3])
elif name == "John" and bank == "PNC Bank": return success([4])
elif name == "John" and bank == "Wells Fargo": return success([5, 6])
elif name == "Alex" and bank == "TD Bank": return success([7, 8])
else: return error("No account associated with (%s, %s)" % (bank, name))
def get_balance(bank, account):
if bank == "Wells Fargo":
return error("Unable to get balance due to technical issue for %s: %s" % (bank, account))
else:
return success([account * 35000]) #right around 200,000 depending on acct number
def get_qualified_amount(balance):
if balance > 200000:
return success([balance])
else:
return error("Insufficient funds for loan, current balance is %s" % balance)
also looking for ways to improve the code. tagged haskell and clojure because this is idiomatic in these languages, the python community isn’t interested in this.
Combining monads by stacking like this is, in Haskell, using Monad Transformers. Set aside Daniel Wagner’s point that ListT is not a monad for moment. You have two monads with types:
List awhich looks like[x,y,z](Error e) awhich looksx, NoneorNone, errIf you convert one to a monad transformer and combine them, there are two ways:
(ErrorT e) List awhich looks like[ (x,None), (y,None), (None, err) ]ListT (ErrorT e) awhich looks like[x,y,z], NoneorNone, [x,y,z]You wanted a list of pairs, so I expect you want the first form. But your simple test does not agree with this. Your
unitdoes not return a list of pairs as in (1.) but a pair of the list and None which is (2.).So you either have things backwards or you have a more complicated monad in mind. I will try and modify your gist to look like (1.).
I think this code might do what you want:
Output is