2012-04-07 35 views
10

Próbuję skomponować seq-m i error-m, aby wykonać listę rzeczy na temat rzeczy, które mogą zwracać błędy. Moje wyniki mają nieoczekiwane typy, ale poza tym wydaje się, że są sensowne. Mój kod został poniżej eksplodować, ale tutaj jest również working gist.łączenie być może i kolejnych monad: pomieszanie na wyjściu

tutaj jest mój monadycznego logika biznesowa

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) 

wyjście

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

spodziewam się zobaczyć listę krotek - lista jest wynikiem listowego, a każda pozycja na ostatecznej liście powinna być wartością w monadzie błędu (value, error krotka). Dokładnie tak, jakby jeden zbyt duży poziom zagnieżdżenia został usunięty przez seq_bind.

oto moja definicja monad, która jeśli nie jest poprawna, jest bardzo bliska, ponieważ obie monady działają w izolacji, po prostu nie łączone.

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

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

również szuka sposobów, aby poprawić kod. otagował haskell i clojure, ponieważ jest to idiomatyczne w tych językach, społeczność Pythona nie jest tym zainteresowana.

+0

Oto jedna pythonista, która jest zainteresowana. Co się stało z twoim blogiem, Dustinem? –

Odpowiedz

8

Łącząc monads układając w ten sposób jest w Haskell, używając Monad Transformers. Odłóż na bok uwagę Daniela Wagnera, że ​​ListT nie jest monadą na chwilę.Masz dwie monady z rodzajów:

  1. List a który wygląda [x,y,z]
  2. (Error e) a który wygląda x, None lub None, err

Jeśli przekształcić jeden z transformatorem monady i połączyć je, istnieją dwa sposoby:

  1. (ErrorT e) List a która wygląda jak [ (x,None), (y,None), (None, err) ]
  2. ListT (ErrorT e) a który wygląda [x,y,z], None lub None, [x,y,z]

Chciałaś listę par, więc spodziewam chcesz pierwszego formularza. Ale twój prosty test nie zgadza się z tym. Twój unit nie zwraca listy par jak w (1.) ale pary listy i Brak, która jest (2.).

Więc albo masz coś do tyłu, albo masz na myśli bardziej skomplikowaną monadę. Spróbuję i zmodyfikuję twój punkt widzenia tak, aby wyglądał (1.).

myślę, że ten kod może zrobić to, co chcesz:

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) 

Wyjście jest

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

Nie jestem ekspertem Python, ale ta definicja:

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

... czyni mnie bardzo podejrzane. Prawdopodobnie, mf ma zwrócić coś, co jest owinięte w oba monady typu error i seq, z najbardziej zewnętrzną zewnętrzną cechą error; jednak przekazujesz ją do seq_bind, która oczekuje funkcji zwracającej coś z zewnętrzną zewnętrzną wartością seq.

Być może spodoba Ci się źródło transformatorów monadowych ErrorT i LogicT w Haskell, aby dowiedzieć się, jak można to zrobić poprawnie. (Można znaleźć LogicT zaskakująco skomplikowane w porównaniu do tego, czego oczekiwał - to dlatego, że naiwny ListTisn't actually a monad transformer!)

+1

Ta wskazówka była bardzo pomocna, dziękuję bardzo. Tłumaczę transformatory Monady dla Fregego (http://code.google.com/p/frege/) i uważam, że ostrzeżenie w "starym" liście 'ListT' jest dość niepokojące. Dobrze znać prawidłową wersję. – Landei

+2

Zobacz także [ten komentarz na Haskell Reddit] (http://www.reddit.com/r/haskell/comments/ryo5t/combining_monads_in_python_wtf_is_wrong_with_my/c49p72l) autorstwa Tekmo. – dave4420

4

Uwaga: Ludzie na reddit zwróciła mi odśwież mój komentarz tutaj jako odpowiedź.

Odpowiedź Daniela Wagnera, ale opiszę ją tutaj, ponieważ nie będzie to pasowało do komentarza stosu przepełnienia.

Po pierwsze, powinieneś przeczytać Monad Transformers - krok po kroku, jeśli jeszcze tego nie zrobiłeś.

Teraz można oczekiwać typ swojej połączonej monady mają być (używając notacji Haskell):

type Combined r = ListT (Either e) r 

Jeśli nie zrozumieć dlaczego ListT jest na zewnątrz, a następnie przejść na papierze Monad Transformers Połączyłem powyższe przed kontynuowaniem. Pamiętaj, że gdybym runListT wartość typu Combined r, chciałbym uzyskać coś takiego:

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

Na podstawie typu Combined r, możemy wywnioskować, że prawidłowy typ (>>=) w Combined monady będzie:

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

Więc teraz będę udawać, że jestem GHC kompilator obdarzone zdolnością do kompilacji kodu Pythona i spróbować przejść przez funkcję bind i wywnioskować typ Wszystko.Chciałbym wywnioskować z powyższego typu dla (>>=), że typ argumentów byłoby:

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

Potem patrzę na seq_bind, które wnoszę musi mieć typ:

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

... gdzie c musi jeszcze zostać określony. Już Twój kod nie wpisać-Check (zakładając Python miał coś takiego jak typów), ponieważ typ seq_bind ma być:

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

Nie można użyć ListT gdzie funkcja oczekuje listę , więc to twój pierwszy problem. W rzeczywistości nie można uzyskać wiązania ListT z więzienia List. Dotyczy to (prawie) wszystkich transformatorów Monady.

Jednak może wyprowadzić ListT (Either e) wiążą z bind na Either e, a bardziej ogólnie, można czerpać bind dla (Monad m) => ListT m nie wiedząc nic o tym, co baza monada jesteś otaczania innego niż to ma działanie (>>=) i return które są posłuszne prawom monady.

Jednak jest to nie banalne, aby napisać prawidłową implementację ListT i wiele odważnych dusz popełniło błąd. Rzeczywiście, ListT, który jest dostarczany ze standardowymi pakietami transformatorów Monada firmy Haskell, to zła i nie jest transformatorem ani monady, ani monady. Prawidłowe wdrożenie, które zdecydowanie popierają, to ten podany tutaj:

ListT done right

Należy szopka z tego kodu (co jest nieco brzydki, ale w 100% poprawne) napisać odpowiedni transformator ListT monady. Nie ulegaj pokusie napisania monadowego transformatora, który natychmiast zwraca listę: gwarantuję, że nie zadziała i nie będzie działać.

+0

Ponieważ napisał: "oczekuję, że zobaczą się listy krotek - lista jest wynikiem zrozumienia list, a każda pozycja na liście końcowej powinna być wartością w monadzie błędu (wartość, krotka błędu)." Myślę, że chce, żeby monady były ułożone w innej kolejności. –

+1

Tak. Opierałem się na porządku, że zastosował swoje dwie jednostki i kolejność jego powiązań, nie opierając się na tym, co powiedział, że chciał, co było dokładnie odwrotne. –

Powiązane problemy