Мені сказали, що у функціональному програмуванні ніхто не повинен кидати та / або спостерігати за винятками. Натомість помилковий розрахунок слід оцінювати як нижнє значення. У Python (або інших мовах, які не повністю заохочують функціональне програмування) можна повертатись None
(або інша альтернатива, яка трактується як нижнє значення, хоча None
і не суворо відповідає визначенню), коли щось піде не так, щоб "залишатися чистим", але робити тому треба спостерігати помилку в першу чергу, тобто
def fn(*args):
try:
... do something
except SomeException:
return None
Це порушує чистоту? І якщо так, то чи означає це, що в Python не можна суто обробляти помилки?
Оновлення
У своєму коментарі Ерік Ліпперт нагадав мені про інший спосіб поводження з винятками у ПП. Хоча я ніколи не бачив того, що робилося на Python на практиці, я грав із ним ще тоді, коли рік тому вивчав FP. Тут будь-яка optional
декорована функція повертає Optional
значення, які можуть бути порожніми, як для нормальних виходів, так і для визначеного списку винятків (невизначені винятки все ще можуть завершити виконання). Carry
створює затримку оцінки, де кожен крок (затримка виклику функції) або отримує непорожній Optional
вихід з попереднього кроку і просто передає його, або іншим чином оцінює себе, передаючи новий Optional
. Зрештою, остаточне значення є або нормальним, або Empty
. Тут try/except
блок прихований за декоратором, тому зазначені винятки можна розглядати як частину підпису типу повернення.
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