วิธีที่สะอาดที่สุดในการรายงานข้อผิดพลาดใน Haskell


22

ฉันทำงานเกี่ยวกับการเรียนรู้ Haskell และฉันเจอวิธีการจัดการกับข้อผิดพลาดสามวิธีในฟังก์ชั่นที่ฉันเขียน:

  1. ฉันสามารถเขียนได้ง่ายerror "Some error message."ซึ่งมีข้อยกเว้น
  2. ฉันสามารถรับฟังก์ชั่นการส่งคืนMaybe SomeTypeซึ่งฉันสามารถหรือไม่สามารถคืนสิ่งที่ฉันต้องการคืนได้
  3. ฉันสามารถใช้ฟังก์ชันส่งคืนEither String SomeTypeได้ซึ่งฉันสามารถส่งคืนข้อความแสดงข้อผิดพลาดหรือสิ่งที่ถูกขอให้ส่งคืนได้ตั้งแต่แรก

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

ความเข้าใจปัจจุบันของฉันคือ:

  • มันเป็น "ยาก" ที่จะจัดการกับข้อยกเว้นในรหัสการทำงานอย่างหมดจดและใน Haskell หนึ่งต้องการให้สิ่งต่าง ๆ ทำงานได้อย่างหมดจดที่สุด
  • การกลับมาMaybe SomeTypeเป็นสิ่งที่เหมาะสมที่จะทำถ้าฟังก์ชั่นจะล้มเหลวหรือประสบความสำเร็จ (เช่นไม่มีวิธีที่แตกต่างกันที่มันจะล้มเหลว )
  • การกลับมาEither String SomeTypeเป็นสิ่งที่ควรทำหากฟังก์ชั่นสามารถล้มเหลวได้หลายวิธี

คำตอบ:


32

เอาล่ะกฎข้อแรกของการจัดการผิดพลาดใน Haskell: ไม่เคยใช้error

มันแย่มากในทุก ๆ ด้าน มันมีอยู่จริงเป็นการกระทำของประวัติศาสตร์และความจริงที่ว่าพรีลูดใช้มันแย่มาก อย่าใช้มัน

ช่วงเวลาที่เป็นไปได้เพียงอย่างเดียวที่คุณสามารถใช้งานได้ก็คือเมื่อมีบางสิ่งที่เลวร้ายมากภายในตัวเองซึ่งบางสิ่งต้องผิดไปจากสิ่งที่เป็นจริงมาก ๆ

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

รูปแบบการจัดการข้อผิดพลาดที่แข็งแกร่งที่สุดคือEither+ ข้อผิดพลาด ADT

ตัวอย่างเช่นหนึ่งในคอมไพเลอร์งานอดิเรกของฉันฉันมีสิ่งที่ชอบ

data CompilerError = ParserError ParserError
                   | TCError TCError
                   ...
                   | ImpossibleError String

data ParserError = ParserError (Int, Int) String
data TCError = CouldntUnify Ty Ty
             | MissingDefinition Name
             | InfiniteType Ty
             ...

type ErrorM m = ExceptT CompilerError m -- from MTL

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

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

ในที่สุดฉันก็บรรจุประเภทนี้เข้าExceptTเป็น monad Transformer ใหม่จาก MTL นี่เป็นสิ่งสำคัญEitherTและมาพร้อมกับชุดฟังก์ชั่นที่ดีสำหรับการขว้างและรับข้อผิดพลาดอย่างบริสุทธิ์และน่าพึงพอใจ

ในที่สุดมันก็คุ้มค่าที่จะกล่าวถึงว่า Haskell มีกลไกในการรองรับการจัดการข้อยกเว้นเช่นภาษาอื่น ๆ ยกเว้นว่าจะมีข้อยกเว้นเกิดIOขึ้น ฉันรู้ว่าบางคนชอบที่จะใช้สิ่งเหล่านี้สำหรับIOแอปพลิเคชันขนาดใหญ่ซึ่งทุกอย่างอาจล้มเหลวได้ แต่บ่อยครั้งที่พวกเขาไม่ชอบที่จะคิด ไม่ว่าคุณจะใช้ข้อยกเว้นที่ไม่บริสุทธิ์เหล่านี้หรือเพียงแค่ExceptT Error IOเป็นเรื่องของรสนิยม ส่วนตัวฉันเลือกExceptTเพราะฉันชอบที่จะได้รับการเตือนถึงโอกาสของความล้มเหลว


โดยสรุป

  • Maybe - ฉันสามารถล้มเหลวในวิธีที่ชัดเจนอย่างใดอย่างหนึ่ง
  • Either CustomType - ฉันสามารถล้มเหลวได้และฉันจะบอกคุณว่าเกิดอะไรขึ้น
  • IO+ ข้อยกเว้น - บางครั้งฉันก็ล้มเหลว ตรวจสอบเอกสารของฉันเพื่อดูสิ่งที่ฉันโยนเมื่อฉันทำ
  • error - ฉันเกลียดคุณเหมือนกัน

คำตอบนี้จะเคลียร์มากสำหรับฉัน ฉันคาดเดาตัวเองเป็นครั้งที่สองจากความจริงที่ว่าฟังก์ชั่นในตัวเช่นheadหรือlastดูเหมือนจะใช้errorดังนั้นฉันจึงสงสัยว่านี่เป็นวิธีที่ดีในการทำสิ่งต่าง ๆ หรือไม่และฉันก็ขาดอะไรบางอย่างไป คำถามนี้ตอบคำถามนี้ :)
CmdrMoozy

5
@CmdrMoozy ดีใจที่ได้ช่วย :) มันโชคร้ายจริงๆที่โหมโรงนั้นมีวิธีปฏิบัติที่ไม่ดีเช่นนั้น นี่คือวิธีการดั้งเดิม: /
Daniel Gratzer

3
+1 สรุปของคุณน่าทึ่ง
recursion.ninja

2

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

นอกเหนือจากนั้นมันไม่ชัดเจนทันทีสำหรับฉันที่Either String SomeTypeสามารถสร้างเงื่อนไขข้อผิดพลาด ฉันจะสร้างข้อมูลพีชคณิตแบบง่ายพร้อมเงื่อนไขที่มีชื่อที่อธิบายได้มากกว่า

สิ่งที่คุณใช้นั้นขึ้นอยู่กับลักษณะของปัญหาที่คุณเผชิญและสำนวนของชุดซอฟต์แวร์ที่คุณใช้งานด้วย แม้ว่าฉันจะหลีกเลี่ยง # 1


ที่จริงแล้วการใช้Eitherข้อผิดพลาดเป็นรูปแบบที่รู้จักกันดีใน Haskell
Rufflewind

1
@runchwind - Eitherไม่ใช่ส่วนที่ไม่ชัดเจนการใช้Stringเป็นข้อผิดพลาดแทนที่จะเป็นสตริงจริง
Telastyn

อาฉันเข้าใจผิดเจตนาของคุณ
Rufflewind
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.