Tôi đã được thông báo rằng trong lập trình chức năng, người ta không được phép ném và / hoặc quan sát các ngoại lệ. Thay vào đó, một tính toán sai lầm nên được đánh giá là giá trị dưới cùng. Trong Python (hoặc các ngôn ngữ khác không hoàn toàn khuyến khích lập trình chức năng), người ta có thể trả về None
(hoặc một ngôn ngữ thay thế khác được coi là giá trị dưới cùng, mặc dù None
không tuân thủ đúng định nghĩa) bất cứ khi nào có lỗi xảy ra với "vẫn thuần túy", nhưng phải làm vì vậy người ta phải quan sát một lỗi ở nơi đầu tiên, tức là
def fn(*args):
try:
... do something
except SomeException:
return None
Điều này có vi phạm sự tinh khiết? Và nếu vậy, điều đó có nghĩa là, không thể xử lý các lỗi hoàn toàn trong Python?
Cập nhật
Trong bình luận của mình, Eric Lippert đã nhắc nhở tôi về một cách khác để xử lý các trường hợp ngoại lệ trong FP. Mặc dù tôi chưa bao giờ thấy điều đó được thực hiện trong Python trong thực tế, tôi đã chơi với nó khi tôi học FP một năm trước. Ở đây, bất kỳ optional
hàm -decorated nào cũng trả về Optional
các giá trị, có thể để trống, cho các đầu ra bình thường cũng như cho một danh sách các trường hợp ngoại lệ được chỉ định (các ngoại lệ không xác định vẫn có thể chấm dứt thực thi). Carry
tạo ra một đánh giá bị trì hoãn, trong đó mỗi bước (lệnh gọi hàm bị trì hoãn) sẽ nhận được một Optional
đầu ra không trống từ bước trước đó và chỉ đơn giản là chuyển nó đi, hoặc nếu không thì tự đánh giá nó chuyển qua một cái mới Optional
. Cuối cùng, giá trị cuối cùng là bình thường hoặc Empty
. Ở đây, try/except
khối được ẩn đằng sau một bộ trang trí, vì vậy các ngoại lệ được chỉ định có thể được coi là một phần của chữ ký loại trả về.
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