2012-04-07 41 views
10

我想编写seq -m和错误-m做可以返回错误的事情的列表解析。我的输出有意想不到的类型,但除此之外它确实看起来是明智的。我在下面爆炸了我的代码,但这里也是working gist结合可能和seq monads:困惑在输出

这里是我的单子的业务逻辑

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') 

我希望看到的元组的名单 - 名单是列表解析的结果,并在最后的列表中每个项目应该是错误monad中的值(value, error元组)。它就好像太多的嵌套级别被seq_bind删除了一样。

这里是我对单子的定义,如果它不正确,它非常接近,因为这两个单子独立工作,只是没有组合。

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)) 

单子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

那么这里至少有一位感兴趣的pythonista。你的博客Dustin发生了什么? – 2016-08-07 12:54:19

回答

8

是,在Haskell,使用Monad Transformers通过堆叠这样的结合单子。抛开Daniel Wagner的观点,即ListT不是一时的单子。你有两个单子与类型:

  1. List a它看起来像[x,y,z]
  2. (Error e) a看起来x, NoneNone, err

如果转换一个单子变压器和它们组合起来,有两种方法:

  1. (ErrorT e) List a看起来像[ (x,None), (y,None), (None, err) ]
  2. ListT (ErrorT e) a它看起来像[x,y,z], NoneNone, [x,y,z]

您想对的列表,所以我希望你要第一种形式。但是你的简单测试不同意这一点。您的unit不会像(1.)中那样返回对的列表,而是(2.)的一对列表和None。

所以,你要么倒退了,要么你有更复杂的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')] 
8

我不是一个Python的专家,但这样的定义:

def bind(mval, mf): 
    return error_bind(mval, lambda mval: seq_bind(mval, mf)) 

...让我非常可疑。据推测,mf应该返回包含在errorseq monad类型中的东西,error-最外层;然而,你将它传递给seq_bind,它需要一个函数返回seq-最外层的内容。

您可能想看看Haskell中的ErrorTLogicT monad变换器的来源,以了解如何正确完成此操作。 (您可能会发现LogicT令人惊讶的复杂相比,你期待什么 - 这是因为幼稚ListTisn't actually a monad transformer!)

+1

这个提示非常有帮助,非常感谢。我正在为Frege翻译monad变形金刚(http://code.google.com/p/frege/),并发现“旧”ListT'中的警告非常令人担忧。很高兴知道正确的版本。 – Landei 2012-04-08 08:05:18

+2

另请参阅Tekmo [此评论在Haskell Reddit](http://www.reddit.com/r/haskell/comments/ryo5t/combining_monads_in_python_wtf_is_wrong_with_my/c49p72l)。 – dave4420 2012-04-08 09:26:26

4

注:在Reddit上的人们请求我在这里重新发布我的评论作为一个答案。

Daniel Wagner的答案,但我会在这里详细说明它,因为这不适合Stack Overflow评论。

首先,你应该阅读Monad变形金刚 - 一步一步,如果你还没有。

现在,你会期望你的单子相结合的类型来(使用哈斯克尔表示法):

type Combined r = ListT (Either e) r 

如果你不明白为什么ListT是在外面,然后再在单子变形金刚纸在继续之前,我将以上链接。请记住,如果我runListTCombined r类型的值,我会得到这样的:

-- Actually, this is WRONG, but see below for the warning about ListT 
runListT (x :: ListT (Either e) r) :: Either e [r] 
基于对 Combined r类型

,我们可以推断,在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其中一个函数期望列表,所以这是你的第一个问题。事实上,根本无法从List绑定中推导出ListT的绑定。对于(几乎)所有单体变压器都是如此。

但是,你可以派生从绑定的ListT (Either e)绑定的Either e,更普遍,你可以得出绑定的(Monad m) => ListT m不知道什么基础单子你包装除具有(>>=)return操作它的其他任何东西遵守monad法律。

但是,它的不是微不足道的写一个正确的ListT执行和许多勇敢的灵魂已经得到它的错误。实际上,带有Haskell标准monad变压器包的ListT错误并且既不是monad也不是monad变压器。正确的实现,这是我非常赞同,在这里给出的一个:

ListT done right

你应该从代码婴儿床(这是一个有点难看,但正确率100%)编写正确的ListT单子转换。不要试图写一个一次返回列表的monad变换器:我保证它不会也不行。

+0

由于他写道:“我希望看到元组列表 - 列表是列表理解的结果,并且最终列表中的每个项目都应该是error-monad(值,错误元组)中的值。”我认为他希望monads能够以另一种顺序堆叠。 – 2012-04-08 18:12:37

+1

是的。我的基础是他申请两个单位的顺序和他的约束顺序,而不是基于他所说的他想要的内容,这完全相反。 – 2012-04-09 01:03:56