Мені сказали, що у функціональному програмуванні ніхто не повинен кидати та / або спостерігати за винятками. Натомість помилковий розрахунок слід оцінювати як нижнє значення. У 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