2015-02-19 9 views
5

Próbuję zaimplementować Monadę Może w python. Jednak to, czego chcę, to rodzaj zdolności do sklejania.Może monada w Pythonie z metodą łańcuchową

Więc mam klasy:

class Maybe: 
    def __init__(self, val): 
     self.val = val 

    def do(self, func): # Bind function 
     if self.val is None: 
      return None 
     else: 
      return func(self.val) 

Mam dwie funkcje:

def double(number): 
    try: 
     result = number * 2 
     return Maybe(result) 
    except: 
     return Maybe(None) 

def square(number): 
    try: 
     result = number * number 
     return Maybe(result) 
    except: 
     return Maybe(None) 

Oto jak używam to:

result = Maybe(5).do(double).do(square) 
    print(result.val) 

ja patrząc na sposób na łańcuchowanie wielu funkcji przy wykonywaniu określonego zadania. Każda funkcja pobiera dane wyjściowe poprzedniej funkcji jako dane wejściowe. Łańcuch powinien się zepsuć, jeśli jakakolwiek funkcja w łańcuchu zgłasza wyjątek.

Czy to właściwy sposób na model Monady Być może?

Czy to również właściwy sposób obsługi wyjątków?

Czy można to poprawić?

Wielkie dzięki.

+0

Co to jest 'Może' ?? – ForceBru

+1

To jest lepsze przy sprawdzaniu kodu, ponieważ nie próbujesz rozwiązać konkretnego problemu i chcesz tylko trochę komentarzy na temat twojego kodu. – OMGtechy

+0

Nie, to nie jest lepiej, ponieważ kod również nie działa: D –

Odpowiedz

2

Minusem jest to, że celowo pomija błędy, które jest powszechnie uważany za zły pomysł w Pythonie.

Można jednak przechwycić i zapisać wszelkie błędy występujące w instancji Maybe i zgłosić je z powrotem.

Na przykład:

class Maybe(object): 
    def __init__(self, val, error=None): 
     self.val = val 
     self.error = error 

    def __repr__(self): 
     if self.val is not None: 
      return repr(self.val) 
     else: 
      return repr(self.error) 

    def do(self, func): 
     if self.val is None: 
      return self 
     try: 
      return Maybe(func(self.val)) 
     except Exception as e: 
      return Maybe(None, e) 

def squared(x): 
    return x * x 

def addone(x): 
    return x + 1 

result1 = Maybe(5).do(squared).do(addone) 
result2 = Maybe('a').do(squared).do(addone) 
print result1 
print result2 

Daje:

26 
TypeError("can't multiply sequence by non-int of type 'str'",) 

ta jest podobna do odpowiedzi DAND, ale ma tę zaletę przechowywania dany błąd zamiast całkowicie tłumiąc go.

Bez względu na to, jak go ukroisz, ten idiom będzie nieco "niepytrynny", ale jest to nieco bardziej solidny sposób radzenia sobie z nim.

+0

Dzięki Joe ... wygląda to jeszcze lepiej ... ale dlaczego nazwałbyś to podejście "niepytotonicznym"? –

+0

Głównie fakt, że błąd nie "rozprzestrzenia się" w górę. Zamiast tego musisz sprawdzić zwróconą wartość. Sprawdzanie wartości zamiast try/except jest ogólnie uważane za niepythoniczne. To niekoniecznie złe, zawsze będzie wydawać się nieco nie na miejscu. Zasadniczo jest to stara argumentacja "patrz, zanim skaczesz", a "łatwiej jest prosić o przebaczenie niż pozwolenie". Python preferuje to drugie. –

4

Następujące czynności wykonują to, o co prosisz, i sprawiają, że funkcje są odrobinę bardziej czyste. aktualizowany także prawidłowo uchwycić wyjątki:

class Maybe: 
    def __init__(self, val, err=None): 
     self.val = val 
     self.err = err 

    def __repr__(self): 
     if self.err is not None: 
      return 'Maybe('+repr(self.val)+', '+repr(self.err)+')' 
     else: 
      return 'Maybe('+repr(self.val)+')' 

    def do(self, func): # Bind function 
     if self.val is not None: 
      try: 
       val = func(self.val) 
      except Exception as e: 
       return Maybe(None, e) 
      if not isinstance(val, Maybe): 
       return Maybe(val) 
      else: 
       return val 
     else: 
      return Maybe(None, self.err) 


def double(number): 
    result = number * 2 
    return result 

def square(number): 
    result = number * number 
    return result 

result = Maybe(5).do(double).do(square) 
print(result.val) 
print(result) 
result2 = Maybe('a').do(double).do(square) 
print(result2.val) 
print(result2) 

Drukuje:

100 
Maybe(100) 
None 
Maybe(None, TypeError("can't multiply sequence by non-int of type 'str'",)) 
+0

Dzięki ... obsługa wyjątków w poszczególnych metodach naprawdę mnie denerwowała. Wygląda to o wiele lepiej. –

2

mógłbym rozważyć kilka korekt:

  • przechowywać wyjątki, jak inni sugerują.
  • Spraw, aby Maybe można było wywołać, aby wyeliminować konieczność użycia do.
  • Przepisz xs i kws przez połączenie, na wypadek, gdyby któraś z twoich funkcji przyjmowała inne argumenty.

Na przykład:

class Maybe: 
    def __init__(self, val, error = None): 
     self.val = val 
     self.error = error 

    def __call__(self, func, *xs, **kws): 
     v = None 
     e = None 
     if self.val is not None: 
      try: 
       v = func(self.val, *xs, **kws) 
      except Exception as e: 
       pass 
     return Maybe(v, error = e) 

def double(n): 
    return n * 2 

def square(n): 
    return n * n 

def multiply_by(n, by): 
    return n * by 

def message(n, *xs, **kws): 
    return 'Message: {} {} {}'.format(n, xs, kws) 

ms = [ 
    Maybe(5)(double)(square), 
    Maybe(4)(square)(square)(double), 
    Maybe(3)(square)(square), 
    Maybe(7)(multiply_by, 3), 
    Maybe(9)(message, 1, 2, 3, foo = 'blah'), 
    Maybe('hi')(double)(square), 
] 

for m in ms: 
    print(dict(val = m.val, error = m.error)) 
+0

Wielkie dzięki. Jestem teraz rozpieszczany! –

3

Ta kwestia została już odpowiedział, ale czułem się za pomocą interfejsu Może monada być bardziej czytelne. Interfejs, który pozwoli na połączenie od lewej do prawej, takie jak:

square(double(Maybe(5))) 

Również byłoby miło zwróci Może wpisać użytkownikowi używać, aby mogli wybrać do dalszego łańcucha zamiast natychmiast pobrać wartość . Ta funkcjonalność jest podobna do opcji typu Swift.

class Maybe(object): 
    def __init__(self, val=None) 
    self.val 

    def unwrap(self): 
    return self.val 

    def calc(self, func): 
    try: 
     return func(self.val) 
    except: 
     return None 

def double(number): 
    return Maybe(maybe.calc(lambda x: x*2)) 

def square(number): 
    return Maybe(maybe.calc(lambda x: x**2)) 

print square(double(Maybe(5))) # prints Maybe type 
print square(double(Maybe(5))).unwrap() # prints '100' 
print square(double(None)) # prints 'None' 

wyjątkami nie są przechowywane w mojej odpowiedzi, ponieważ rozmówca oczekuje maybe monad sprawdzić wyjątkiem siebie i konwertować do „nic” (tj Brak) jeżeli przykuty operacja nie powiedzie się. Pozwala to użytkownikowi łatwo radzić sobie z awariami w warunkach warunkowych za pomocą łańcuchów.