2012-04-07 3 views
10

Ich versuche Seq-m und Fehler-M zu kompilieren, um die Liste auf Fehler, die Fehler zurückgeben können. Meine Ausgabe hat unerwartete Typen, obwohl es tatsächlich sinnvoll erscheint. Ich habe meinen Code unten explodiert, aber hier ist auch ein working gist.kombiniert vielleicht und seq Monaden: verwirrt an der Ausgabe

hier ist meine monadischen Geschäftslogik

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) 

Ausgang

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 erwarten Listen von Tupeln zu sehen - die Liste ist das Ergebnis der Liste Verständnisses, und jedes Element in der endgültigen Liste sollte ein Wert in Fehler-Monad (value, error Tupel) sein. Es ist genau so, als ob zu viele Verschachtelungsebenen von seq_bind entfernt wurden.

Hier ist meine Definition der Monaden, die, wenn es nicht korrekt ist, ist es sehr eng, weil beide Monaden isoliert arbeiten, nur nicht kombiniert.

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

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

auch nach Möglichkeiten, um den Code zu verbessern. getagged haskell und clojure weil das in diesen sprachen idiomatisch ist, interessiert sich die python community nicht dafür.

+0

Nun, hier ist wenigstens ein Pythonist, der interessiert ist. Was ist mit deinem Blog passiert, Dustin? –

Antwort

8

Kombination Monaden durch wie diese Stapel, in Haskell, Monad Transformers verwenden. Lassen Sie Daniel Wagners Argument beiseite, dass ListT für den Moment keine Monade ist.Sie haben zwei Monaden mit Typen:

  1. List a die wie [x,y,z]
  2. (Error e) a sieht die x, None oder None, err

Wenn Sie Transformator umwandeln man zu einer Monade aussieht und kombinieren sie, gibt es zwei Möglichkeiten:

  1. (ErrorT e) List a die aussieht wie [ (x,None), (y,None), (None, err) ]
  2. ListT (ErrorT e) a, die sieht aus wie [x,y,z], None oder None, [x,y,z]

Sie eine Liste von Paaren wollte, also erwarte ich Sie das erste Formular möchten. Aber dein einfacher Test stimmt damit nicht überein. Ihre unit gibt keine Liste von Paaren wie in (1.) zurück, sondern ein Paar der Liste und None, die (2.) ist.

So haben Sie entweder Dinge rückwärts oder Sie haben eine kompliziertere Monade im Hinterkopf. Ich werde versuchen, deinen Kern so zu verändern, dass er wie folgt aussieht (1.).

Ich denke, das Code könnte das tun, was Sie wollen:

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) 

Ausgang ist

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

Ich bin kein Python-Experte, aber diese Definition:

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

... macht mich sehr verdächtig. Vermutlich soll 10 etwas zurückgeben, das sowohl in die error als auch in die seq Monad-Typen eingepackt ist, wobei die error -ness äußerste; Sie übergeben es jedoch an seq_bind, die eine Funktion erwartet, die etwas mit der seq -ness äußerste zurückgibt.

Vielleicht möchten Sie einen Blick auf die Quelle der ErrorT und LogicT Monade-Transformatoren in Haskell werfen, um eine Vorstellung davon zu bekommen, wie dies richtig gemacht werden könnte. (Vielleicht findet LogicT raschend komplizierten im Vergleich zu dem, was Sie erwartet haben - dies, weil die naive ist ListTisn't actually a monad transformer!)

+1

Dieser Hinweis war sehr hilfreich, vielen Dank. Ich übersetze Monade-Transformer für Frege (http://code.google.com/p/freege/) und fand die Warnung in der "alten" 'ListT' alarmierend. Gut, die richtige Version zu kennen. – Landei

+2

Siehe auch [diesen Kommentar zum Haskell Reddit] (http://www.reddit.com/r/haskell/comments/ryo5t/combining_monads_in_python_wtf_is_wrong_with_my/c49p72l) von Tekmo. – dave4420

4

Hinweis: Leute auf reddit bat mich mein Kommentar hier als Antwort auf umbuchen.

Die Antwort von Daniel Wagner, aber ich werde es hier näher ausführen, da dies nicht in einen Stack Overflow Kommentar passt.

Zunächst sollten Sie lesen Monad Transformers - Schritt für Schritt, wenn Sie nicht bereits haben.

Nun, würden Sie die Art Ihrer kombinierten Monade erwarten werden (Haskell-Notation):

type Combined r = ListT (Either e) r 

Wenn Sie nicht verstehen, warum ListT auf der Außenseite ist, dann gehen Sie über die Monade Transformers Papier Ich habe oben verlinkt, bevor ich fortfahre. Denken Sie daran, dass, wenn ich auf runListT ein Wert vom Typ bin Combined r, würde ich so etwas wie:

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

Basierend auf der Art der Combined r, können wir daraus schließen, dass die richtige Art von (>>=) im Combined Monade würde:

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

So, jetzt werde ich behaupte, dass ich bin die GHC Compiler mit der Fähigkeit ausgestattet Python-Code zu kompilieren und versuchen, durch Ihre bind Funktion zu gehen und allen Typen schließen.Ich würde für (>>=) aus der obigen Art schließen, dass die Art des Arguments wäre:

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

Dann sehe ich seq_bind, die ich den Typen haben schließen müssen:

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

... wo c noch bestimmt werden muss. Bereits Code eingeben Prüfung nicht (vorausgesetzt, Python hatte so etwas wie Typen), da die Art der seq_bind sein soll:

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

Sie keine ListT wo eine Funktion eine Liste erwartet verwenden können Das ist dein erstes Problem. Tatsächlich können Sie die Bindung ListT aus der Bindung List überhaupt nicht ableiten. Dies gilt für (fast) alle Monadetransformatoren.

aber Sie können leiten die ListT (Either e) binden aus der Bindung für Either e und allgemeiner, können Sie die bind für (Monad m) => ListT m ableiten, ohne etwas zu wissen, was Basis Monade Sie wickeln andere als eine (>>=) und return Betrieb mit die den Gesetzen der Monade gehorchen.

Allerdings ist es nicht trivial, eine korrekte ListT Implementierung zu schreiben und viele mutige Seelen haben es falsch verstanden. In der Tat ist die ListT, die mit Haskell Standard-Monade-Transformator-Pakete kommt falsch und ist weder ein Monad noch ein Monad Transformator. Die korrekte Umsetzung, die ich stark befürworte, ist die, die hier angegeben:

ListT done right

Du von diesem Code Krippe sollte (was ein bisschen hässlich, aber 100% richtig) einen richtigen ListT Monade Transformator zu schreiben. Versuchen Sie nicht, einen Monad Transformer zu schreiben, der die Liste auf einmal zurückgibt: Ich garantiere Ihnen, dass es nicht funktioniert und nicht funktionieren kann.

+0

Da er schrieb "Ich erwarte Listen von Tupeln zu sehen - die Liste ist das Ergebnis des Listenverständnisses, und jedes Element in der endgültigen Liste sollte ein Wert in Fehler-Monade (Wert, Fehler Tupel) sein." Ich denke, dass er möchte, dass die Monaden in der anderen Reihenfolge gestapelt werden. –

+1

Ja. Ich basierte darauf, dass er seine zwei Einheiten und die Reihenfolge seiner Bindungen anwendete, nicht basierend auf dem, was er sagte, was er wollte, was genau das Gegenteil war. –

Verwandte Themen