我正在尝试编写seq-m和error-m来对可能返回错误的事情进行列表推导。我的输出有意想不到的类型,但除了它实际上似乎是明智的。我在下面展开了我的代码,但这里也是working gist。
这是我的monadic业务逻辑
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)
输出
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')
我希望看到元组列表 - 列表是列表推导的结果,最终列表中的每个项目都应该是error-monad(value, error
元组)中的值。就像seq_bind
删除了太多级别的嵌套一样。
这是我对monad的定义,如果它不正确,它非常接近,因为两个monad都是孤立地工作,而不是合并。
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)
还在寻找改进代码的方法。标记为haskell和clojure,因为这在这些语言中是惯用的,python社区对此不感兴趣。
答案 0 :(得分:8)
我不是Python专家,但这个定义:
def bind(mval, mf):
return error_bind(mval, lambda mval: seq_bind(mval, mf))
...让我非常怀疑。据推测,mf
应该返回包含在error
和seq
monad类型中的内容,其中error
- 最外层;但是,你将它传递给seq_bind
,它需要一个函数返回seq
- 最外层的东西。
您可能希望了解Haskell中ErrorT
和LogicT
monad变换器的来源,以了解如何正确完成此操作。 (与你期望的相比,你可能会发现LogicT
出乎意料地复杂 - 这是因为天真的ListT
isn't actually a monad transformer!)
答案 1 :(得分:8)
在Haskell中,通过像这样堆叠来组合monad是使用Monad Transformers。撇开Daniel Wagner的观点,ListT暂时不是monad。你有两个类型的monad:
List a
看起来像[x,y,z]
(Error e) a
看起来x, None
或None, err
如果将one转换为monad变换器并将它们组合起来,有两种方法:
(ErrorT e) List a
看起来像[ (x,None), (y,None), (None, err) ]
ListT (ErrorT e) a
看起来像[x,y,z], None
或None, [x,y,z]
你想要一对配对列表,所以我希望你想要第一个表格。但是你的简单测试并不同意这一点。您的unit
不会返回(1.)中的对列表,而是返回一对列表和None,即(2。)。
所以你要么让事情倒退,要么你有一个更复杂的monad。我会尝试修改你的要点(1。)。
我认为这段代码可能会做你想要的:
def flatten(listOfLists):
"Flatten one level of nesting"
assert isinstance(listOfLists, list)
if len(listOfLists) > 0:
assert isinstance(listOfLists[0], list)
return [x for sublist in listOfLists for x in sublist]
# sequence monad
def seq_unit(x): return [x]
def seq_bind(mval, mf): return flatten(map(mf, mval))
# Decompose ErrorT e m a
def get_value(m_val): return m_val[0]
def get_error(m_val): return m_val[1]
# hard coded "(ErrorT e) List a" instance of throwError, note that seq_unit is hardcoded
def error_throwError(err): return (None, err)
def errorT_list_throwError(err): return seq_unit(error_throwError(err))
# "(ErrorT e) List a" monad
def error_unit(x): return (x,None)
def errorT_list_unit(x): return seq_unit(error_unit(x))
def error_bind(mval, mf):
assert isinstance(mval, tuple)
error = get_error(mval)
if error:
return error_throwError(error)
else:
return mf(get_value(mval))
# Cannot have multi-line lambda
def errorT_list_bind_helper(mval, mf):
assert isinstance(mval, tuple)
error = get_error(mval)
if error:
return errorT_list_throwError(error)
else:
return mf(get_value(mval))
def errorT_list_bind(mval, mf): return seq_bind(mval, lambda v: errorT_list_bind_helper(v, mf))
# combined monad !! (ErrorT e) List a
unit = errorT_list_unit
bind = errorT_list_bind
throwError = errorT_list_throwError
# hard coded "lift :: List a -> (ErrorT e) List a"
def lift(mval):
assert isinstance(mval, list)
# return [ (val,None) for val in mval ]
# return [ errorT_list_unit(val) for val in mval ]
return seq_bind(mval, lambda v : unit(v))
def get_banks(name):
if name == "Irek": return lift(["Bank of America", "Wells Fargo"])
elif name == "John": return unit("PNC Bank")
elif name == "Alex": return unit("TD Bank")
else: return throwError("No bank associated with name %s" % name)
def get_accounts(bank, name):
if name == "Irek" and bank == "Bank of America": return lift([1, 2])
elif name == "Irek" and bank == "Wells Fargo": return unit(3)
elif name == "John" and bank == "PNC Bank": return unit(4)
elif name == "John" and bank == "Wells Fargo": return lift([5, 6])
elif name == "Alex" and bank == "TD Bank": return lift([7, 8])
else: return throwError("No account associated with (%s, %s)" % (bank, name))
def get_balance(bank, account):
if bank == "Wells Fargo":
return throwError("Unable to get balance due to technical issue for %s: %s" % (bank, account))
else:
return unit(account * 35000) #right around 200,000 depending on acct number
def get_qualified_amount(balance):
if balance > 200000:
return unit(balance)
else:
return throwError("Insufficient funds for loan, current balance is %s" % balance)
# 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))))))
assert isinstance(m_qualified_amounts, list)
assert isinstance(m_qualified_amounts[0], tuple)
return m_qualified_amounts
names = ["Irek", "John", "Alex", "Fred"]
for name, loans in zip(names, map(get_loan, names)):
print "%s: %s" % (name, loans)
输出
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')]
答案 2 :(得分:4)
注意:reddit上的人要求我在此处重新发布我的评论作为答案。
Daniel Wagner的回答,但我会在这里详细说明,因为这不适合Stack Overflow评论。
首先,如果你还没有,你应该阅读Monad Transformers - Step by Step。
现在,您可以期待组合monad的类型(使用Haskell表示法):
type Combined r = ListT (Either e) r
如果您不明白为什么ListT
在外面,那么请继续阅读上面链接的Monad变形金刚论文。请记住,如果我要runListT
Combined r
类型的值,我会得到类似的内容:
-- Actually, this is WRONG, but see below for the warning about ListT
runListT (x :: ListT (Either e) r) :: Either e [r]
根据Combined r
的类型,我们可以推断(>>=)
monad中Combined
的正确类型为:
(>>=) :: ListT (Either e) a -> (a -> ListT (Either e) b) -> ListT (Either e) b
所以现在我假装我是GHC
编译器,它具有编译Python代码的能力,并尝试通过你的bind
函数并推断出所有类型。我会从(>>=)
的上述类型推断出参数的类型是:
mval :: ListT (Either e) a
mf :: a -> ListT (Either e b)
然后我看seq_bind
,我推断它必须具有类型:
seq_bind :: ListT (Either e) a -> (a -> ListT (Either e) b) -> c
...其中c
尚未确定。你的代码已经没有类型检查(假设Python有类型的东西),因为seq_bind的类型应该是:
seq_bind :: [a] -> (a -> [b]) -> [b]
您不能使用函数需要列表的ListT
,这是您的第一个问题。实际上,根本无法从ListT
绑定派生List
绑定。对于(几乎)所有monad变换器都是如此。
但是,您可以从ListT (Either e)
的绑定派生Either e
绑定,更一般地说,您可以在不知道任何内容的情况下派生(Monad m) => ListT m
的绑定关于你使用(>>=)
和return
操作遵守monad法则的基础monad你包装的内容。
然而,编写正确的ListT
实现并且不是微不足道的,并且许多勇敢的灵魂已经弄错了。事实上,Haskell的标准monad变压器包附带的ListT
错误,既不是monad也不是monad变换器。我强烈支持的正确实施是这里给出的:
您应该从该代码(有点难看,但100%正确)中编写一个正确的ListT
monad变换器。不要试图写一个monad变换器一次性返回列表:我保证你不会也不会工作。