함수형 프로그래밍에서 예외를 던지거나 관찰해서는 안된다는 말을 들었습니다. 대신 잘못된 계산은 최저값으로 평가해야합니다. 파이썬 (또는 함수형 프로그래밍을 완전히 장려하지 않는 다른 언어) 에서는 무언가가 "순수하게 유지"되는 것이 잘못 될 때마다 리턴 할 수 있습니다 None
(또는 None
정의를 엄격하게 준수하지는 않지만 다른 값을 최저값으로 취급 함 ). 먼저 오류를 관찰해야합니다. 즉
def fn(*args):
try:
... do something
except SomeException:
return None
이것이 순결을 위반합니까? 그렇다면 파이썬에서 순수하게 오류를 처리하는 것이 불가능하다는 것을 의미합니까?
최신 정보
그의 의견에서 Eric Lippert는 FP에서 예외를 처리하는 또 다른 방법을 상기시켰다. 실제로 파이썬에서 한 일을 본 적이 없지만 1 년 전에 FP를 공부했을 때 나는 그것을 가지고 놀았습니다. 여기서 optional
-decorated 함수는 Optional
지정된 예외 목록뿐만 아니라 일반 출력에 대해 비어있을 수있는 값을 리턴 합니다 (지정되지 않은 예외는 여전히 실행을 종료 할 수 있음). Carry
각 단계 (지연된 함수 호출)가 Optional
이전 단계에서 비어 있지 않은 출력을 가져 와서 단순히 전달하거나 그렇지 않으면 new를 전달하여 평가하는 지연된 평가를 작성합니다 Optional
. 결국 최종 값은 normal 또는 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