10

エラーを返す可能性のあるもののリスト内包表記を行うために、seq-m と error-m を構成しようとしています。私の出力には予期しないタイプがありますが、それ以外は実際には賢明なようです。私は以下のコードを展開しましたが、ここにも作業の要点があります。

ここに私のモナドビジネスロジックがあります

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タプル)の値である必要があります。1 つ多すぎるレベルのネストが によって削除されたかのよう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 コミュニティはこれに関心がありません。

4

3 に答える 3

8

このようにスタックしてモナドを結合することは、Haskell ではMonad Transformersを使用しています。ListT はモナドではないという Daniel Wagner の指摘は、しばらく脇に置いておきます。型を持つ 2 つのモナドがあります。

  1. List a次のように見えます[x,y,z]
  2. (Error e) aどちらに見えるx, NoneNone, err

1 つをモナド変換子に変換して組み合わせる場合、次の 2 つの方法があります。

  1. (ErrorT e) List a次のように見えます[ (x,None), (y,None), (None, err) ]
  2. ListT (ErrorT e) a[x,y,z], Noneまたは_None, [x,y,z]

ペアのリストが必要だったので、最初のフォームが必要になると思います。しかし、あなたの簡単なテストはこれに同意しません。( 1. unit) のようにペアのリストを返すのではなく、(2.) であるリストと None のペアを返します。

したがって、物事を逆方向に持っているか、より複雑なモナドを念頭に置いています。私はあなたの要点を (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')]
于 2012-04-08T07:15:07.733 に答える
8

私は Python の専門家ではありませんが、この定義は次のとおりです。

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

…とても疑問に思います。おそらく、はとモナド型mfの両方にラップされたものを返し、 -ness が最も外側になるはずです。ただし、それを に渡しています。これは、最も外側の -nessで何かを返す関数を期待しています。errorseqerrorseq_bindseq

ErrorTHaskell の theおよびモナド トランスフォーマーのソースを見て、LogicTこれがどのように正しく行われるかを理解することをお勧めします。(LogicT予想していたものと比べて驚くほど複雑に感じるかもしれません -- これは、ナイーブListT が実際にはモナド変換子ではないためです!)

于 2012-04-07T23:48:01.840 に答える
4

注: reddit の人々は、回答としてここに私のコメントを再投稿するように私に要求しました。

Daniel Wagner による回答ですが、これは Stack Overflow のコメントに収まらないため、ここで詳しく説明します。

まず、Monad Transformers - Step by Step をまだ読んでいない場合は読むべきです。

ここで、結合されたモナドの型は (Haskell 記法を使用して) 次のようになると予想されます。

type Combined r = ListT (Either e) r

が外側にある理由がわからない場合はListT、先に進む前に、上でリンクした Monad Transformers の論文を読んでください。runListTtype の値にすると、次の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の正しい型は次のようになると推測できます。(>>=)Combined

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

そこで、私はGHCPython コードをコンパイルする能力を備えたコンパイラであると仮定して、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はまだ決まっていません。seq_bind の型は次のようになっているはずなので、すでにコードは型チェックを行っていません (Python に型などがあると仮定します)。

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

ListT関数がリストを期待する場所で aを使用することはできないため、これが最初の問題です。ListT実際、バインドから のバインドを派生させることはまったくできませんList。これは (ほぼ) すべてのモナド変換子に当てはまります。

ただし、bind for から bind を導出することはできます。より一般的には、モナドの法則に従うand操作を持っていること以外は、ラップしている基本モナドについて何も知らなくても、bind for を導出できます。ListT (Either e)Either e(Monad m) => ListT m(>>=)return

しかし、正しい実装を書くことは簡単ではなくListT、多くの勇敢な魂がそれを間違えています. 実際、ListTHaskell の標準モナド変換パッケージに付属する は間違っており、モナドでもモナド変換子でもありません。私が強く支持する正しい実装は、次のとおりです。

ListT は正しく行われました

ListT適切なモナド変換子を書くには、そのコード (少し醜いですが、100% 正しい) から抜粋する必要があります。リストを一度に返すようなモナド変換子を書きたがらないでください。

于 2012-04-08T14:31:44.513 に答える