การจัดการข้อยกเว้นสไตล์การทำงาน


24

ฉันได้รับการบอกว่าในการเขียนโปรแกรมทำงานไม่ควรโยนและ / หรือสังเกตข้อยกเว้น ควรคำนวณการคำนวณที่ผิดพลาดแทนค่าล่าง ในภาษาไพ ธ อน (หรือภาษาอื่นที่ไม่สนับสนุนการเขียนโปรแกรมเชิงฟังก์ชันอย่างสมบูรณ์) เราสามารถส่งคืน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

1
คำถามของคุณได้รับการตอบแล้วดังนั้นเพียงแค่ความเห็น: คุณเข้าใจหรือไม่ว่าทำไมการที่ฟังก์ชั่นของคุณมีข้อผิดพลาดเกิดขึ้นในการเขียนโปรแกรมการทำงาน ไม่ใช่ราชประสงค์โดยพลการ :)
Andres F.

6
มีอีกทางเลือกหนึ่งในการส่งคืนค่าที่ระบุถึงข้อผิดพลาด โปรดจำไว้ว่าการจัดการข้อยกเว้นคือโฟลว์ควบคุมและเรามีกลไกการทำงานสำหรับการแสดงโฟลว์ควบคุมอีกครั้ง คุณสามารถเลียนแบบการจัดการในภาษาการทำงานข้อยกเว้นโดยการเขียนวิธีการของคุณจะใช้เวลาสองฟังก์ชั่นคือความต่อเนื่องความสำเร็จและความต่อเนื่องข้อผิดพลาด สิ่งสุดท้ายที่ฟังก์ชั่นของคุณทำคือเรียกความสำเร็จต่อเนื่องผ่าน "ผลลัพธ์" หรือความต่อเนื่องของข้อผิดพลาดผ่าน "ข้อยกเว้น" ข้อเสียคือคุณต้องเขียนโปรแกรมของคุณในลักษณะนี้จากภายในสู่ภายนอก
Eric Lippert

3
ไม่อะไรจะเป็นรัฐในกรณีนี้? มีปัญหาหลายอย่าง แต่มีอยู่สองสามข้อ: 1- มีการไหลที่เป็นไปได้หนึ่งอย่างซึ่งไม่ได้เข้ารหัสในประเภทนั้นคือคุณไม่สามารถรู้ได้ว่าฟังก์ชันจะส่งข้อยกเว้นเพียงแค่ดูที่ประเภท (เว้นแต่คุณได้ตรวจสอบเท่านั้น แน่นอนว่ามีข้อยกเว้น แต่ฉันไม่รู้ภาษาใด ๆ ที่มีเฉพาะพวกเขา) คุณกำลังทำงานอย่างมีประสิทธิภาพ "นอก" ระบบประเภทโปรแกรมเมอร์ที่ใช้งานได้ 2 คนพยายามเขียนฟังก์ชั่น "รวม" ทุกครั้งที่ทำได้เช่นฟังก์ชันที่ส่งคืนค่าสำหรับอินพุตทุกตัว (ยกเว้นข้อ จำกัด ) ข้อยกเว้นทำงานกับสิ่งนี้
Andres F.

3
3- เมื่อคุณทำงานกับฟังก์ชั่นทั้งหมดคุณสามารถเขียนมันด้วยฟังก์ชั่นอื่น ๆ และใช้งานในลำดับที่สูงขึ้นโดยไม่ต้องกังวลเกี่ยวกับผลลัพธ์ข้อผิดพลาด "ไม่ได้เข้ารหัสในประเภท" เช่นข้อยกเว้น
Andres F.

1
@EliKorvigo การตั้งค่าตัวแปรแนะนำสถานะตามคำนิยามหยุดเต็ม วัตถุประสงค์ทั้งหมดของตัวแปรคือพวกเขาถือสถานะ มันไม่มีส่วนเกี่ยวข้องกับข้อยกเว้น การสังเกตค่าส่งคืนของฟังก์ชั่นและการตั้งค่าตัวแปรตามการสังเกตแนะนำสถานะในการประเมินใช่มั้ย
user253751

คำตอบ:


20

ก่อนอื่นเรามาทำความเข้าใจกับความเข้าใจผิดกันก่อน ไม่มี "ค่าล่าง" ประเภทด้านล่างถูกกำหนดให้เป็นประเภทที่เป็นประเภทย่อยของทุกประเภทอื่น ๆ ในภาษา จากนี้เราสามารถพิสูจน์ได้ (ในระบบการพิมพ์ใด ๆ ที่น่าสนใจอย่างน้อย) ว่าประเภทด้านล่างมีค่าไม่มี - มันเป็นที่ว่างเปล่า ดังนั้นจึงไม่มีสิ่งเช่นค่าล่าง

ทำไมประเภทด้านล่างจึงมีประโยชน์ การรู้ว่ามันว่างเปล่าเรามาทำการลดพฤติกรรมของโปรแกรม ตัวอย่างเช่นถ้าเรามีฟังก์ชั่น:

def do_thing(a: int) -> Bottom: ...

เรารู้ว่าไม่สามารถกลับมาเพราะมันจะต้องกลับมาเป็นค่าของชนิดdo_thing Bottomดังนั้นจึงมีเพียงสองความเป็นไปได้:

  1. do_thing ไม่หยุด
  2. do_thing โยนข้อยกเว้น (ในภาษาที่มีกลไกการยกเว้น)

โปรดทราบว่าฉันสร้างประเภทBottomที่ไม่มีอยู่จริงในภาษา Python Noneเป็นชื่อเรียกผิด จริงๆแล้วมันคือค่าหน่วย , ค่าเดียวของหน่วยประเภท , ซึ่งเรียกว่าNoneTypeใน Python (ทำtype(None)เพื่อยืนยันด้วยตัวคุณเอง)

ตอนนี้ความเข้าใจผิดอื่นคือภาษาที่ใช้งานได้ไม่มีข้อยกเว้น สิ่งนี้ไม่เป็นความจริงเช่นกัน ตัวอย่าง SML มีกลไกข้อยกเว้นที่ดีมาก อย่างไรก็ตามมีการใช้ข้อยกเว้นใน SML มากกว่าใน Python ดังที่คุณได้กล่าวไปแล้ววิธีทั่วไปในการระบุถึงความล้มเหลวบางประการในภาษาที่ใช้งานได้คือการส่งคืนOptionประเภท ตัวอย่างเช่นเราจะสร้างฟังก์ชั่นการแบ่งอย่างปลอดภัยดังนี้:

def safe_div(num: int, den: int) -> Option[int]:
  return Some(num/den) if den != 0 else None

น่าเสียดายเนื่องจาก Python ไม่มีประเภทผลรวมนี่จึงไม่ใช่วิธีที่ปฏิบัติได้ คุณสามารถกลับมาNoneเป็นชนิดตัวเลือกที่น่าสงสารคนของความล้มเหลวมีความหมาย Nullแต่นี่มันไม่ดีกว่ากลับมา ไม่มีความปลอดภัยประเภท

ดังนั้นฉันจะแนะนำให้ทำตามอนุสัญญาภาษาในกรณีนี้ Python ใช้ข้อยกเว้นที่เป็นไปในทางที่ผิดเพื่อจัดการโฟลว์ควบคุม (ซึ่งเป็นรูปแบบที่ไม่ดี IMO แต่เป็นมาตรฐานอย่างไรก็ตาม) เว้นแต่ว่าคุณจะทำงานกับโค้ดที่คุณเขียนเองเท่านั้น ไม่ว่าจะเป็น "บริสุทธิ์" หรือไม่เกี่ยวข้อง


โดย "bottom value" ฉันหมายถึง "bottom type" นั่นคือเหตุผลที่ฉันเขียนที่Noneไม่สอดคล้องกับคำจำกัดความ อย่างไรก็ตามขอขอบคุณที่แก้ไขฉัน คุณไม่คิดว่าการใช้ข้อยกเว้นเพียงเพื่อหยุดการดำเนินการทั้งหมดหรือส่งคืนค่าที่เลือกได้นั้นเป็นไปตามหลักการของ Python ใช่ไหม ฉันหมายความว่าเหตุใดจึงไม่ดีที่จะยับยั้งการใช้ข้อยกเว้นสำหรับการควบคุมที่ซับซ้อน
Eli Korvigo

@EliKorvigo นั่นคือสิ่งที่ฉันพูดมากกว่านี้ใช่มั้ย ข้อยกเว้นคือ Python ที่ใช้สำนวน
Gardenhead

1
ตัวอย่างเช่นฉันไม่สนับสนุนให้นักศึกษาระดับปริญญาตรีของฉันใช้try/except/finallyอีกทางเลือกหนึ่งif/elseเช่นtry: var = expession1; except ...: var = expression 2; except ...: var = expression 3...แม้ว่ามันจะเป็นเรื่องธรรมดาที่จะทำในภาษาที่จำเป็น (btw ฉันก็ไม่แนะนำให้ใช้if/elseบล็อกนี้เช่นกัน) คุณหมายถึงว่าฉันไม่มีเหตุผลและควรอนุญาตรูปแบบดังกล่าวตั้งแต่ "นี่คืองูหลาม" หรือไม่?
Eli Korvigo

@EliKorvigo ฉันเห็นด้วยกับคุณโดยทั่วไป (btw คุณเป็นอาจารย์หรือไม่?) ไม่try... catch...ควรใช้สำหรับการควบคุมการไหล ด้วยเหตุผลบางอย่างนั่นคือวิธีที่ชุมชน Python ตัดสินใจทำสิ่งต่างๆ ยกตัวอย่างเช่นฟังก์ชั่นที่ผมเขียนข้างต้นจะมักจะเขียน ดังนั้นหากคุณกำลังสอนหลักการทั่วไปเกี่ยวกับวิศวกรรมซอฟต์แวร์คุณควรไม่สนับสนุนสิ่งนี้ มันเป็นกลิ่นรหัส แต่มันก็ยาวเกินไปที่จะเข้าไปที่นี่ safe_divtry: result = num / div: except ArithmeticError: result = Noneif ... else...
Gardenhead

2
มี "ค่าต่ำสุด" (ใช้สำหรับพูดถึงความหมายของ Haskell เป็นต้น) และมันมีส่วนเกี่ยวข้องกับประเภทล่าง นั่นไม่ใช่ความเข้าใจผิดของ OP แต่พูดถึงสิ่งที่แตกต่าง
Ben

11

เนื่องจากมีความสนใจอย่างมากในช่วงสองสามวันที่ผ่านมาทำไมเราไม่ตรวจสอบฟังก์ชั่นที่บริสุทธิ์

ฟังก์ชั่นบริสุทธิ์:

  • มีความโปร่งใสในการอ้างอิง นั่นคือสำหรับอินพุตที่กำหนดมันจะสร้างเอาต์พุตเดียวกันเสมอ

  • ไม่ก่อให้เกิดผลข้างเคียง ไม่เปลี่ยนอินพุตเอาต์พุตหรือสิ่งอื่นใดในสภาพแวดล้อมภายนอก มันสร้างค่าตอบแทน

ดังนั้นถามตัวเอง ฟังก์ชั่นของคุณทำอะไรนอกจากยอมรับอินพุตและคืนเอาต์พุตหรือไม่


3
ดังนั้นมันไม่สำคัญว่ามันน่าเกลียดแค่ไหนที่ถูกเขียนไว้ข้างในตราบเท่าที่มันทำงานตามหน้าที่?
Eli Korvigo

8
ถูกต้องหากคุณสนใจเพียงความบริสุทธิ์ แน่นอนอาจมีสิ่งอื่นที่ต้องพิจารณา
Robert Harvey

3
ในการเล่นผู้สนับสนุนปีศาจฉันขอยืนยันว่าการโยนข้อยกเว้นเป็นเพียงแค่รูปแบบของผลลัพธ์และหน้าที่ที่มีความบริสุทธิ์ นั่นเป็นปัญหาหรือไม่?
Bergi

1
@Bergi ฉันไม่รู้ว่าคุณกำลังเล่นเป็นผู้สนับสนุนของปีศาจเพราะนั่นคือสิ่งที่คำตอบนี้หมายถึง :) ปัญหาคือว่ามีข้อควรพิจารณาอื่นนอกเหนือจากความบริสุทธิ์ ถ้าคุณอนุญาตให้ยกเว้นไม่ จำกัด (ซึ่งโดยความหมายไม่ได้เป็นส่วนหนึ่งของลายเซ็นของฟังก์ชัน) แล้วพิมพ์กลับของทุกฟังก์ชั่นได้อย่างมีประสิทธิภาพจะกลายเป็นT + { Exception }(ที่Tมีการประกาศอย่างชัดเจนชนิดกลับ) ซึ่งเป็นปัญหา คุณไม่สามารถรู้ได้ว่าฟังก์ชั่นจะเกิดข้อผิดพลาดหรือไม่หากไม่ได้ดูซอร์สโค้ดของมัน
Andres F.

2
@Briri ในขณะที่การขว้างอาจเป็นเนื้อหาที่บริสุทธิ์ IMO ที่ใช้การยกเว้นนั้นไม่ได้ซับซ้อนกว่าประเภทการคืนของแต่ละฟังก์ชัน มันทำลายความสามารถในการเรียงหน้าที่ของฟังก์ชั่น พิจารณาการดำเนินการmap : (A->B)->List A ->List Bที่A->Bผิดพลาด ถ้าเราช่วยให้ f เพื่อโยนยกเว้นจะกลับมาบางสิ่งบางอย่างจากประเภทmap f L Exception + List<B>หากเราอนุญาตให้ส่งคืนoptionalสไตล์map f Lจะเป็นการส่งคืนรายการ <ตัวเลือก <B>> `แทน ตัวเลือกที่สองนี้ให้ประโยชน์แก่ฉันมากกว่า
Michael Anderson

7

Haskell semantics ใช้ "bottom value" เพื่อวิเคราะห์ความหมายของรหัส Haskell ไม่ใช่สิ่งที่คุณใช้โดยตรงในการเขียนโปรแกรม Haskell และการกลับมาNoneไม่ใช่สิ่งเดียวกัน

ค่าด้านล่างคือค่าที่กำหนดโดยความหมายของ Haskell ในการคำนวณใด ๆ ที่ล้มเหลวในการประเมินค่าตามปกติ วิธีหนึ่งในการคำนวณ Haskell สามารถทำได้โดยการโยนข้อยกเว้น! ดังนั้นหากคุณพยายามใช้สไตล์นี้ใน Python คุณควรโยนข้อยกเว้นตามปกติ

ความหมายของ Haskell ใช้ค่าด้านล่างเพราะ Haskell ขี้เกียจ คุณสามารถจัดการ "ค่า" ที่ส่งคืนโดยการคำนวณที่ยังไม่ได้ทำงานจริง คุณสามารถส่งผ่านให้พวกเขาฟังก์ชั่นติดไว้ในโครงสร้างข้อมูลอื่น ๆ เช่นการคำนวณ unevaluated อาจโยนยกเว้นหรือห่วงตลอดไป แต่ถ้าเราไม่เคยต้องการจริงเพื่อตรวจสอบค่าแล้วคำนวณจะไม่เคยเรียกใช้และพบข้อผิดพลาดและโปรแกรมโดยรวมของเราอาจจัดการเพื่อทำสิ่งที่กำหนดไว้อย่างดีและเสร็จสิ้น ดังนั้นโดยไม่ต้องการที่จะอธิบายว่ารหัส Haskell หมายถึงอะไรโดยการระบุพฤติกรรมการทำงานที่แน่นอนของโปรแกรมที่รันไทม์เราจึงขอประกาศว่าการคำนวณที่ผิดพลาดดังกล่าวสร้างมูลค่าที่ต่ำที่สุดและอธิบายว่าค่าดังกล่าวทำงานอย่างไร โดยทั่วไปว่าการแสดงออกใด ๆ ที่ต้องขึ้นอยู่กับคุณสมบัติใด ๆ ของมูลค่าล่าง (อื่น ๆ กว่าที่มันมีอยู่) จะยังส่งผลให้เกิดความคุ้มค่าด้านล่าง

ในการคงไว้ซึ่ง "บริสุทธิ์" วิธีการที่เป็นไปได้ทั้งหมดในการสร้างมูลค่าด้านล่างจะต้องได้รับการปฏิบัติเหมือนกัน ซึ่งรวมถึง "ค่าล่าง" ที่แสดงถึงการวนซ้ำไม่สิ้นสุด เนื่องจากไม่มีทางที่จะรู้ได้ว่าลูปไม่มีที่สิ้นสุดบางอันแท้จริงไม่มีที่สิ้นสุด (พวกเขาอาจเสร็จสิ้นหากคุณใช้พวกเขาอีกสักครู่) คุณจึงไม่สามารถตรวจสอบคุณสมบัติใด ๆ ที่มีค่าต่ำที่สุด คุณไม่สามารถทดสอบได้ว่ามีบางอย่างอยู่ด้านล่างไม่สามารถเปรียบเทียบกับสิ่งอื่นไม่สามารถแปลงเป็นสตริงได้ไม่มีอะไร สิ่งที่คุณสามารถทำได้คือใส่ไว้ในตำแหน่ง (พารามิเตอร์ฟังก์ชั่นส่วนหนึ่งของโครงสร้างข้อมูล ฯลฯ ) ไม่มีการแตะต้องและไม่ได้ตรวจสอบ

Python มีระดับล่างสุดอยู่แล้ว เป็น "คุณค่า" ที่คุณได้รับจากนิพจน์ที่ส่งข้อยกเว้นหรือไม่ยุติ เนื่องจาก Python นั้นเข้มงวดมากกว่าสันหลังยาวจึงไม่สามารถจัดเก็บ "พื้น" ดังกล่าวได้ทุกที่และอาจไม่มีการตรวจสอบ ดังนั้นจึงไม่จำเป็นต้องใช้แนวคิดของค่าล่างเพื่ออธิบายว่าการคำนวณที่ล้มเหลวในการส่งคืนค่ายังสามารถได้รับการปฏิบัติราวกับว่าพวกเขามีค่า แต่ก็ไม่มีเหตุผลที่คุณไม่สามารถคิดแบบนี้เกี่ยวกับข้อยกเว้นได้ถ้าคุณต้องการ

ข้อยกเว้นการขว้างปาถือว่าจริง ๆ แล้ว "บริสุทธิ์" มันจับข้อยกเว้นที่ทำให้เกิดความบริสุทธิ์ - อย่างแม่นยำเพราะช่วยให้คุณสามารถตรวจสอบบางสิ่งบางอย่างเกี่ยวกับค่าที่ต่ำกว่าแทนที่จะทำการรักษาพวกมันทั้งหมดแทนกันได้ ใน Haskell คุณสามารถตรวจจับข้อยกเว้นในส่วนIOที่อนุญาตให้มีการเชื่อมต่อที่ไม่บริสุทธิ์ (ดังนั้นจึงมักเกิดขึ้นที่ชั้นนอกสุดพอสมควร) Python ไม่บังคับใช้ความบริสุทธิ์ แต่คุณยังสามารถตัดสินใจด้วยตัวเองว่าฟังก์ชั่นใดเป็นส่วนหนึ่งของ "เลเยอร์ไม่บริสุทธิ์ชั้นนอก" ของคุณมากกว่าฟังก์ชั่นที่บริสุทธิ์และอนุญาตให้คุณจับข้อยกเว้นที่นั่นเท่านั้น

การกลับมาNoneแทนนั้นแตกต่างอย่างสิ้นเชิง Noneเป็นค่าที่ไม่ใช่ด้านล่าง; คุณสามารถทดสอบได้ว่ามีอะไรที่เท่ากับหรือไม่และผู้เรียกฟังก์ชั่นที่ส่งคืนNoneจะยังคงทำงานต่อไปอาจใช้สิ่งที่Noneไม่เหมาะสม

ดังนั้นหากคุณคิดจะขว้างข้อยกเว้นและต้องการ "ย้อนกลับล่าง" เพื่อเลียนแบบวิธีการของ Haskell คุณไม่ต้องทำอะไรเลย ปล่อยให้ข้อยกเว้นแพร่กระจาย นั่นคือสิ่งที่โปรแกรมเมอร์ Haskell หมายถึงเมื่อพวกเขาพูดถึงฟังก์ชั่นที่ส่งคืนค่าต่ำสุด

แต่นั่นไม่ใช่สิ่งที่โปรแกรมเมอร์ทำหน้าที่หมายถึงเมื่อพวกเขาพูดเพื่อหลีกเลี่ยงข้อยกเว้น โปรแกรมเมอร์ฟังก์ชั่นต้องการ "ฟังก์ชั่นรวม" สิ่งเหล่านี้จะส่งคืนค่าที่ไม่ใช่ด้านล่างที่ถูกต้องของประเภทผลตอบแทนของพวกเขาสำหรับทุกอินพุตที่เป็นไปได้ ฟังก์ชั่นใด ๆ ที่สามารถโยนข้อยกเว้นไม่ใช่ฟังก์ชั่นทั้งหมด

เหตุผลที่เราชอบฟังก์ชั่นทั้งหมดก็คือพวกมันง่ายกว่าที่จะถือเป็น "กล่องดำ" เมื่อเรารวมและจัดการมัน ถ้าฉันมีฟังก์ชั่นทั้งหมดคืนบางสิ่งบางอย่างของประเภท A และฟังก์ชั่นรวมที่ยอมรับบางสิ่งบางอย่างประเภท A จากนั้นฉันสามารถเรียกที่สองในการส่งออกของครั้งแรกโดยไม่ต้องรู้อะไรเกี่ยวกับการดำเนินการอย่างใดอย่างหนึ่ง; ฉันรู้ว่าฉันจะได้รับผลลัพธ์ที่ถูกต้องไม่ว่าโค้ดของฟังก์ชันใดฟังก์ชันหนึ่งจะได้รับการอัปเดตในอนาคต (ตราบใดที่จำนวนทั้งหมดยังคงอยู่และตราบเท่าที่ยังคงลายเซ็นประเภทเดียวกันไว้) การแยกข้อกังวลนี้อาจเป็นเครื่องมือที่ทรงพลังอย่างยิ่งสำหรับการปรับโครงสร้างใหม่

มันก็ค่อนข้างจำเป็นสำหรับฟังก์ชั่นการสั่งซื้อที่สูงขึ้น (ฟังก์ชั่นที่จัดการกับฟังก์ชั่นอื่น ๆ ) ถ้าฉันต้องการเขียนโค้ดที่ได้รับฟังก์ชั่นโดยพลการ (พร้อมกับอินเตอร์เฟสที่รู้จัก) เป็นพารามิเตอร์ฉันต้องปฏิบัติมันเป็นกล่องดำเพราะฉันไม่มีทางรู้ว่าอินพุตใดที่อาจทำให้เกิดข้อผิดพลาด ถ้าฉันได้รับฟังก์ชั่นทั้งหมดแล้วไม่มีการป้อนข้อมูลจะทำให้เกิดข้อผิดพลาด ในทำนองเดียวกันผู้เรียกฟังก์ชั่นการสั่งซื้อที่สูงขึ้นของฉันจะไม่ทราบว่าสิ่งที่ขัดแย้งที่ฉันใช้ในการโทรฟังก์ชั่นที่พวกเขาผ่านฉัน (เว้นแต่พวกเขาต้องการที่จะขึ้นอยู่กับรายละเอียดการใช้งานของฉัน) ดังนั้นผ่านฟังก์ชั่นทั้งหมด ฉันทำอะไรกับมัน

ดังนั้นโปรแกรมเมอร์ที่ใช้งานได้ซึ่งแนะนำให้คุณหลีกเลี่ยงข้อยกเว้นจะต้องการให้คุณส่งคืนค่าที่เข้ารหัสข้อผิดพลาดหรือค่าที่ถูกต้องและต้องการให้คุณใช้มันคุณพร้อมที่จะจัดการกับความเป็นไปได้ทั้งสองอย่าง สิ่งที่ต้องการEitherหรือประเภทMaybe/ Optionชนิดคือบางส่วนของวิธีการที่ง่ายที่สุดในการทำเช่นนี้ในภาษาพิมพ์มากขึ้นอย่างมาก (โดยปกติจะใช้กับไวยากรณ์พิเศษหรือฟังก์ชั่นการสั่งซื้อที่สูงขึ้นเพื่อสิ่งช่วยเหลือกาวกันว่าต้องAกับสิ่งที่ผลิตMaybe<A>)

ฟังก์ชั่นที่ส่งกลับNone(ถ้ามีข้อผิดพลาดเกิดขึ้น) หรือค่าบางอย่าง (หากไม่มีข้อผิดพลาด) กำลังติดตามทั้งกลยุทธ์ข้างต้น

ใน Python ที่มีเป็ดพิมพ์สไตล์ Either / อาจไม่ได้ใช้มากนักแทนที่จะปล่อยให้ข้อยกเว้นถูกทดสอบด้วยการทดสอบเพื่อตรวจสอบว่ารหัสทำงานได้ดีกว่าฟังก์ชั่นที่ไว้ใจได้ทั้งหมด Python ไม่มีเครื่องมืออำนวยความสะดวกในการบังคับใช้รหัสนั้นใช้ประเภทอย่างบางทีอาจจะเหมาะสม แม้ว่าคุณจะใช้มันเป็นเรื่องของวินัยคุณต้องทำการทดสอบเพื่อใช้รหัสของคุณในการตรวจสอบ ดังนั้นข้อยกเว้น / วิธีล่างจึงเหมาะกับการเขียนโปรแกรมที่ใช้งานได้จริงใน Python


1
+1 คำตอบที่ยอดเยี่ยม! และครอบคลุมมากเช่นกัน
Andres F.

6

ตราบใดที่ไม่มีผลข้างเคียงที่มองเห็นได้จากภายนอกและค่าตอบแทนขึ้นอยู่กับอินพุตจากนั้นฟังก์ชั่นนั้นบริสุทธิ์แม้ว่ามันจะทำสิ่งที่ไม่บริสุทธิ์ภายใน

ดังนั้นมันขึ้นอยู่กับสิ่งที่สามารถทำให้เกิดข้อยกเว้นได้อย่างแน่นอน หากคุณกำลังพยายามเปิดไฟล์ที่ระบุพา ธ แล้วไม่มันไม่บริสุทธิ์เพราะไฟล์อาจมีหรือไม่มีอยู่ซึ่งจะทำให้ค่าส่งคืนแตกต่างกันไปสำหรับอินพุตเดียวกัน

ในทางกลับกันหากคุณพยายามแยกจำนวนเต็มจากสตริงที่กำหนดและโยนข้อยกเว้นหากล้มเหลวนั่นอาจเป็นความบริสุทธิ์ตราบใดที่ไม่มีข้อยกเว้นใด ๆ

ในหมายเหตุด้านภาษาที่ใช้งานได้มักจะส่งคืนหน่วยประเภทก็ต่อเมื่อมีเงื่อนไขข้อผิดพลาดที่เป็นไปได้เพียงครั้งเดียวเท่านั้น หากมีข้อผิดพลาดที่เป็นไปได้หลายข้อพวกเขามักจะส่งคืนประเภทข้อผิดพลาดพร้อมข้อมูลเกี่ยวกับข้อผิดพลาด


คุณไม่สามารถส่งคืนประเภทด้านล่าง
Gardenhead

1
@gardenhead คุณพูดถูกฉันคิดถึงประเภทยูนิต คงที่
8bittree

ในกรณีของตัวอย่างแรกของคุณระบบไฟล์ไม่ได้และมีไฟล์ใดอยู่ภายในเพียงแค่หนึ่งในอินพุตของฟังก์ชัน
Vality

2
@Vaility ในความเป็นจริงคุณอาจมีจุดจับหรือพา ธ ไปยังไฟล์ หากฟังก์ชั่นfนั้นบริสุทธิ์คุณจะf("/path/to/file")ต้องคืนค่าเดิมเสมอ เกิดอะไรขึ้นถ้าไฟล์จริงได้รับการลบหรือเปลี่ยนแปลงระหว่างสองสวดเพื่อf?
Andres F.

2
@Vaility ฟังก์ชั่นอาจบริสุทธิ์ถ้าแทนที่จะจัดการกับไฟล์ที่คุณผ่านมันเนื้อหาจริง (เช่น snapshot ของไบต์ที่แน่นอน) ของไฟล์ซึ่งในกรณีนี้คุณสามารถรับประกันได้ว่าหากเนื้อหาเดียวกันไปในผลลัพธ์เดียวกัน ออกไป. แต่การเข้าถึงไฟล์นั้นไม่เป็นเช่นนั้น :)
Andres F.
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.