2012-04-07 10 views
10

Sto provando a comporre seq-me error-m per fare liste di comprensione su cose che possono restituire errori. Il mio output ha tipi inaspettati, anche se a parte questo sembra davvero sensato. ho esploso il mio codice qui sotto, ma qui c'è anche il working gist.combinando forse e seq monadi: confuso all'uscita

qui è la mia logica di business monadica

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) 

uscita

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

mi aspetto di vedere gli elenchi di tuple - l'elenco è il risultato della lista di comprensione, e ogni elemento della lista finale dovrebbe essere un valore in error-monad (value, error tuple). È esattamente come se uno o troppi livelli di nidificazione venissero rimossi dal seq_bind.

ecco la mia definizione delle monadi, che se non è corretta, è molto vicina perché entrambe le monadi lavorano in isolamento, ma non sono combinate.

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 monadica

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) 

anche alla ricerca di modi per migliorare il codice. taggato haskell e clojure perché questo è idiomatico in questi linguaggi, la comunità python non è interessata a questo.

+0

Beh, ecco un pitone almeno che è interessato. Cosa è successo al tuo blog, Dustin? –

risposta

8

Combinando monadi impilando come questo è, in Haskell, utilizzando Monad Transformers. Metti da parte il punto di Daniel Wagner che ListT non è una monade per il momento.Hai due monadi con tipi:

  1. List a che assomiglia [x,y,z]
  2. (Error e) a che sembra x, None o None, err

Se si converte uno a un trasformatore monade e combinarli, ci sono due modi:

  1. (ErrorT e) List a che assomiglia [ (x,None), (y,None), (None, err) ]
  2. ListT (ErrorT e) a che si presenta come [x,y,z], None o None, [x,y,z]

Volevi una lista di coppie, quindi mi aspetto che si desidera che la prima forma. Ma il tuo semplice test non è d'accordo con questo. Il tuo unit non restituisce un elenco di coppie come in (1.) ma una coppia della lista e None che è (2.).

Quindi avete o cose indietro o avete una mente più complicata in mente. Proverò a modificare il tuo aspetto per assomigliare (1.).

penso che questo codice potrebbe fare quello che vuoi:

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) 

uscita è

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

io non sono un esperto di Python, ma questa definizione:

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

... mi fa molto sospetto. Presumibilmente, si suppone che mf restituisca qualcosa racchiuso in entrambi i tipi di monade error e seq, con il valore error -ness esterno; tuttavia, lo stai passando a seq_bind, che si aspetta una funzione che restituisce qualcosa con lo seq -ness più esterno.

Potrebbe essere utile dare un'occhiata alla fonte dei trasformatori monad e LogicT in Haskell per avere un'idea di come questo potrebbe essere eseguito correttamente. (Si potrebbe trovare LogicT sorprendentemente complicato rispetto a quello che ti aspettavi - questo è perché l'ingenuo ListTisn't actually a monad transformer!)

+1

Quel suggerimento è stato molto utile, grazie mille. Sto traducendo i trasformatori monad per Frege (http://code.google.com/p/frege/) e ho trovato abbastanza allarmante l'avviso nel "vecchio" ListT'. Buono a sapersi la versione corretta. – Landei

+2

Vedere anche [questo commento su Haskell Reddit] (http://www.reddit.com/r/haskell/comments/ryo5t/combining_monads_in_python_wtf_is_wrong_with_my/c49p72l) di Tekmo. – dave4420

4

Nota: La gente su Reddit mi ha chiesto di ripubblicare il mio commento qui come una risposta.

La risposta di Daniel Wagner, ma qui approfondirò perché questo non si adatta ad un commento di Overflow dello stack.

Prima di tutto, dovresti leggere Monad Transformers - Step by Step se non lo hai già fatto.

Ora, ci si aspetta il tipo di monade combinati per essere (usando la notazione Haskell):

type Combined r = ListT (Either e) r 

Se non si capisce il motivo per cui ListT è al di fuori, quindi andare oltre la carta Monade Transformers Ho collegato sopra prima di procedere. Ricordate che se dovessi runListT un valore di tipo Combined r, vorrei avere qualcosa di simile:

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

In base al tipo di Combined r, possiamo dedurre che il tipo corretto di (>>=) nel Combined Monade sarebbe:

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

Così ora farò finta che io sono il compilatore GHC dotato della possibilità di compilare il codice Python e cercare di passare attraverso la vostra funzione bind e dedurre il tipo di cosa.Vorrei deduco dal tipo sopra per (>>=), che il tipo degli argomenti sarebbe:

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

poi guardo seq_bind, che deduco deve avere il tipo:

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

... dove c deve ancora essere determinato. Già il codice non digita-check (assumendo Python ha avuto una cosa come i tipi), in quanto il tipo di seq_bind dovrebbe essere:

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

Non è possibile utilizzare un ListT in cui una funzione si aspetta una lista , quindi questo è il tuo primo problema. In effetti, non è possibile derivare il bind di ListT dall'associazione List. Questo è vero per (quasi) tutti i trasformatori monad.

Tuttavia, è possibile derivare il ListT (Either e) bind dal legano per Either e, e più in generale, è possibile derivare il binding per (Monad m) => ListT m senza sapere nulla di quello che di base monade si sta disposizione oltre che esso abbia un'operazione (>>=) e return che obbediscono alle leggi della monade.

Tuttavia, è non banale per scrivere un'implementazione corretta ListT e molte anime coraggiose hanno sbagliato. Infatti lo ListT fornito con i pacchetti di trasformatori monad standard di Haskell è errato e non è né un monad né un trasformatore monad. La corretta attuazione, che ho fortemente condivido, è quella data qui:

ListT done right

Si dovrebbe presepe da quel codice (che è un po 'brutto, ma corretta al 100%) a dare una corretta trasformatore di ListT monade. Non essere tentato di scrivere un trasformatore monad che restituisce l'elenco tutto in una volta: ti garantisco che non funzionerà e non potrà funzionare.

+0

Poiché ha scritto "mi aspetto di vedere le liste di tuple - la lista è il risultato della comprensione della lista, e ogni elemento nella lista finale dovrebbe essere un valore in error-monad (value, error tuple)." Penso che voglia impilare le monadi nell'altro ordine. –

+1

Sì. Lo stavo basando sull'ordine in cui applicava le sue due unità e l'ordine dei suoi legami, non in base a ciò che diceva di volere, il che era esattamente l'opposto. –