ฉันได้รับการบอกว่าในการเขียนโปรแกรมทำงานไม่ควรโยนและ / หรือสังเกตข้อยกเว้น ควรคำนวณการคำนวณที่ผิดพลาดแทนค่าล่าง ในภาษาไพ ธ อน (หรือภาษาอื่นที่ไม่สนับสนุนการเขียนโปรแกรมเชิงฟังก์ชันอย่างสมบูรณ์) เราสามารถส่งคืนNone
(หรืออีกทางเลือกหนึ่งที่ถือว่าเป็นค่าที่ต่ำที่สุด แต่None
ไม่ปฏิบัติตามคำจำกัดความอย่างเคร่งครัด) เมื่อใดก็ตามที่บางสิ่งผิดปกติ ดังนั้นหนึ่งจะต้องสังเกตข้อผิดพลาดในสถานที่แรกคือ
def fn(*args):
try:
... do something
except SomeException:
return None
สิ่งนี้เป็นการละเมิดความบริสุทธิ์หรือไม่ และถ้าเป็นเช่นนั้นหมายความว่าเป็นไปไม่ได้ที่จะจัดการกับข้อผิดพลาดใน Python อย่างแท้จริงหรือไม่?
ปรับปรุง
ในความคิดเห็นของเขา Eric Lippert ทำให้ฉันนึกถึงอีกวิธีหนึ่งในการรักษาข้อยกเว้นใน FP แม้ว่าฉันจะไม่เคยเห็นแบบนั้นใน Python ในทางปฏิบัติ แต่ฉันก็เล่นกับมันเมื่อฉันศึกษา FP เมื่อปีที่แล้ว optional
ฟังก์ชันใด ๆ-decorated จะส่งคืน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