2012-04-07 14 views
10

で混乱が、私はエラーを返すことができるものにリストの内包表記を行うためのseq-Mおよびエラー-Mを構成しようとしています。私の出力には予期しないタイプがありますが、それ以外は実際には分かりやすいようです。私は下のコードを分解しましたが、ここにはworking gistもあります。ここ多分組み合わせ、配列モナド:出力

は私のモナドのビジネスロジックが

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

である私は、タプルのリストを見ることを期待 - リストは、リストの内包表記の結果であり、最終的なリストの各項目エラーモナド(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に何が起こったのですか? –

答えて

8

は、Haskellで、Monad Transformersを使用している。このように積層してモナドを組み合わせる - (これが原因でナイーブListTisn't actually a monad transformerです!あなたは、あなたが期待していたものと比較LogicTが驚くほど複雑かもしれません)。ダニエル・ワグナーの指摘は、リストTは瞬間的にはモナドではないことを忘れてしまった。あなたがモナド変換子に1を変換し、それらを組み合わせた場合x, Noneまたは

None, errに見える[x,y,z]

  • (Error e) aのように見えます

    1. List aは、二つの方法があります:あなたはタイプを持つ2つのモナドがあります。

      1. (ErrorT e) List a[ (x,None), (y,None), (None, err) ]のように見えるあなたはペアのリストを望んでいたので、私はあなたが最初のフォームが欲しい期待

    2. [x,y,z], Noneようまたは None, [x,y,z]に見える
    3. ListT (ErrorT e) a。しかし、あなたの簡単なテストはこれに同意しません。 unitは、(1)のようなペアのリストを返しませんが、リストのペアと(2.)のNoneを返します。

      あなたは後方に物事を持っているか、より複雑なモナドを念頭に置いています。あなたの要点を(1)のように修正しようとします。

      私は、このコードは、あなたがやりたいかもしれないと思う:redditの上の人々は答えとしてここに私のコメントを再投稿するために私を要請:

      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は、最も外側のerror -nessで、errorseqモナド型の両方に包まれています何かを返すことになっています。しかし、あなたはseq -ness最も外側で何かを返す関数を期待する、seq_bindにそれを渡しています。

    ハスケルのErrorTLogicTモナド・トランスのソースを見て、これが正しく行われる方法を知りたい場合があります。

    +1

    そのヒントはとても役に立ちました、ありがとうございました。私はFrege(http://code.google.com/p/frege/)のモナド変圧器を翻訳していて、 "古い" ListTの警告が非常に驚いています。正しいバージョンを知っていることをお勧めします。 – Landei

    +2

    Tekmoの[Haskell Redditに関するこのコメント](http://www.reddit.com/r/haskell/comments/ryo5t/combining_monads_in_python_wtf_is_wrong_with_my/c49p72l)も参照してください。 – dave4420

    4

    注意です。

    Daniel Wagnerの回答ですが、スタックオーバーフローのコメントには当てはまりませんので、ここで詳しく説明します。

    まず、Monad Transformers - Step by Stepを読んでください。

    さて、あなたは(Haskellの表記法を使用して)組み合わせモナドの種類があることを期待する:

    type Combined r = ListT (Either e) r 
    

    ListTが外側にある理由を理解していない場合は、モナド変圧器の紙の上に行きます私は先に進む前にリンクしています。 Combined rのタイプに基づいて

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

    、我々はCombinedモナドで(>>=)の正しい型があろうと推測することができます:私はrunListTにタイプCombined rの値だった場合、私のようなものになるだろう、ということを覚えておいてください

    (>>=) :: ListT (Either e) a -> (a -> ListT (Either e) b) -> ListT (Either e) b 
    

    だから今、私はPythonコードをコンパイルしてbind機能を通過し、すべてのタイプを推測しようとする能力に恵まれGHCコンパイラだとふります。私はタイプ持っている必要があります推測され、その後、私はseq_bind

    mval :: ListT (Either e) a 
    mf :: a -> ListT (Either e b) 
    

    :私は、引数の型があろうと、(>>=)については、上記のタイプから推測するでしょう

    seq_bind :: ListT (Either e) a -> (a -> ListT (Either e) b) -> c 
    

    を... cはまだ決定されていない。すでにあなたのコードは、入力チェックをしないseq_bindの種類があることを想定しているので、(Pythonのタイプのようなものを持っていたと仮定した場合):

    seq_bind :: [a] -> (a -> [b]) -> [b] 
    

    をあなたは関数がリストを期待ListTを使用することはできませんそれがあなたの最初の問題です。実際には、ListTのバインドをListバインドからまったく引き出すことはできません。これは(ほぼ)すべてのモナド変圧器に当てはまります。

    ただし、はEither eのためにバインドからListT (Either e)バインドを導き出すことができ、そしてより一般的に、あなたはそれが(>>=)return操作を持つ以外にラップされているものをベースモナドについて何も知らずに(Monad m) => ListT mためにバインドを導き出すことができますモナドの法律に従う。

    しかし、それはではないを書くのは簡単ではありません。ListTの実装と多くの勇敢な魂は間違っています。実際、Haskellの標準モナド変換パッケージに付属のListTが間違っていますであり、モナドでもモナドトランスでもありません。

    ListT done right

    あなたはそのコードからベビーベッドなければならない(正しいビット醜いですが、100%)適切なListTモナド変換子を書くために:私は強く支持し正しい実装は、ここで与えられたものです。一度にリストを返すモナド・トランスフォーマーを書こうと誘惑しないでください:私はあなたにそれが動作しないことを保証します。

    +0

    彼は「私はタプルのリストを見ることを期待しています。リストはリストの理解の結果であり、最終リストの各アイテムはエラーモナド(値、エラータプル)の値でなければなりません。彼はモナドを他の順序で積み重ねたいと思う。 –

    +1

    うん。私はそれが彼の2つのユニットと彼のバインドの順序を適用した順序に基づいていた、彼が望んだとは言わないで、それは正反対でした。 –