Powiedziano mi, że w programowaniu funkcjonalnym nie należy rzucać i / lub obserwować wyjątków. Zamiast tego błędne obliczenia należy ocenić jako dolną wartość. W Pythonie (lub innych językach, które nie w pełni zachęcają do programowania funkcjonalnego) można zwrócić None
(lub inną alternatywę traktowaną jako dolna wartość, choć None
nie jest ściśle zgodna z definicją), gdy coś pójdzie nie tak, aby „pozostać czystym”, ale zrobić więc należy przede wszystkim zauważyć błąd, tj
def fn(*args):
try:
... do something
except SomeException:
return None
Czy to narusza czystość? A jeśli tak, to czy oznacza to, że niemożliwe jest obsługiwanie błędów wyłącznie w języku Python?
Aktualizacja
W swoim komentarzu Eric Lippert przypomniał mi o innym sposobie traktowania wyjątków w FP. Chociaż nigdy nie widziałem tego w Pythonie w praktyce, bawiłem się nim, kiedy studiowałem FP rok temu. Tutaj dowolna optional
funkcja -dekorowana zwraca Optional
wartości, które mogą być puste, zarówno dla normalnych danych wyjściowych, jak i dla określonej listy wyjątków (nieokreślone wyjątki nadal mogą zakończyć wykonywanie). Carry
tworzy opóźnioną ocenę, w której każdy krok (opóźnione wywołanie funkcji) albo otrzymuje niepuste Optional
wyjście z poprzedniego kroku i po prostu przekazuje go, lub w inny sposób ocenia siebie, przekazując nowy Optional
. Ostatecznie wartość końcowa jest albo normalna, albo Empty
. Tutaj try/except
blok jest ukryty za dekoratorem, więc określone wyjątki można traktować jako część podpisu typu zwracanego.
class Empty:
def __repr__(self):
return "Empty"
class Optional:
def __init__(self, value=Empty):
self._value = value
@property
def value(self):
return Empty if self.isempty else self._value
@property
def isempty(self):
return isinstance(self._value, BaseException) or self._value is Empty
def __bool__(self):
raise TypeError("Optional has no boolean value")
def optional(*exception_types):
def build_wrapper(func):
def wrapper(*args, **kwargs):
try:
return Optional(func(*args, **kwargs))
except exception_types as e:
return Optional(e)
wrapper.__isoptional__ = True
return wrapper
return build_wrapper
class Carry:
"""
>>> from functools import partial
>>> @optional(ArithmeticError)
... def rdiv(a, b):
... return b // a
>>> (Carry() >> (rdiv, 0) >> (rdiv, 0) >> partial(rdiv, 1))(1)
1
>>> (Carry() >> (rdiv, 0) >> (rdiv, 1))(1)
1
>>> (Carry() >> rdiv >> rdiv)(0, 1) is Empty
True
"""
def __init__(self, steps=None):
self._steps = tuple(steps) if steps is not None else ()
def _add_step(self, step):
fn, *step_args = step if isinstance(step, Sequence) else (step, )
return type(self)(steps=self._steps + ((fn, step_args), ))
def __rshift__(self, step) -> "Carry":
return self._add_step(step)
def _evaluate(self, *args) -> Optional:
def caller(carried: Optional, step):
fn, step_args = step
return fn(*(*step_args, *args)) if carried.isempty else carried
return reduce(caller, self._steps, Optional())
def __call__(self, *args):
return self._evaluate(*args).value