Cách sạch nhất để báo cáo lỗi trong Haskell


22

Tôi đang nghiên cứu về Haskell và tôi đã tìm ra ba cách khác nhau để xử lý lỗi trong các chức năng tôi viết:

  1. Tôi chỉ có thể viết error "Some error message.", mà ném một ngoại lệ.
  2. Tôi có thể trả lại chức năng của mình Maybe SomeType, nơi tôi có thể hoặc không thể trả lại những gì tôi muốn trả lại.
  3. Tôi có thể trả lại chức năng của mình Either String SomeType, nơi tôi có thể trả về một thông báo lỗi hoặc những gì tôi được yêu cầu trả lại ở vị trí đầu tiên.

Câu hỏi của tôi là: Tôi nên sử dụng phương pháp xử lý lỗi nào và tại sao? Có lẽ tôi nên phương pháp khác nhau, tùy thuộc vào bối cảnh?

Hiểu biết hiện tại của tôi là:

  • Thật là "khó khăn" để đối phó với các ngoại lệ trong mã chức năng thuần túy, và trong Haskell, người ta muốn giữ mọi thứ hoàn toàn có chức năng nhất có thể.
  • Trở về Maybe SomeTypelà điều thích hợp để làm nếu chức năng sẽ thất bại hoặc thành công (nghĩa là không có những cách khác nhau mà nó có thể thất bại ).
  • Trở về Either String SomeTypelà điều thích hợp để làm nếu một chức năng có thể thất bại theo bất kỳ một trong những cách khác nhau.

Câu trả lời:


32

Được rồi, quy tắc xử lý lỗi đầu tiên trong Haskell: Không bao giờ sử dụngerror .

Nó chỉ là khủng khiếp về mọi mặt. Nó tồn tại hoàn toàn như một hành động của lịch sử và thực tế là Prelude sử dụng nó thật kinh khủng. Đừng sử dụng nó.

Thời gian có thể tưởng tượng duy nhất mà bạn có thể sử dụng là khi một thứ gì đó quá khủng khiếp đến nỗi có gì đó không ổn với chính kết cấu thực tế, do đó đưa ra kết quả của chương trình của bạn.

Bây giờ câu hỏi trở thành Maybevs Either. Maybelà rất phù hợp với một cái gì đó như head, có thể hoặc không thể trả về một giá trị nhưng chỉ có một lý do có thể để thất bại. Nothingđang nói điều gì đó như "nó đã phá vỡ, và bạn đã biết tại sao". Một số người sẽ nói nó chỉ ra một phần chức năng.

Hình thức xử lý lỗi mạnh mẽ nhất là Either+ ADT lỗi.

Ví dụ, trong một trong những trình biên dịch sở thích của tôi, tôi có một cái gì đó như

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

Bây giờ tôi xác định một loạt các loại lỗi, lồng chúng để tôi có một lỗi cấp cao nhất. Đây có thể là một lỗi từ bất kỳ giai đoạn biên dịch hoặc một ImpossibleError, điều này biểu thị một lỗi biên dịch.

Mỗi loại lỗi này cố gắng giữ càng nhiều thông tin càng lâu càng tốt để in đẹp hoặc phân tích khác. Quan trọng hơn, bằng cách không có một chuỗi, tôi có thể kiểm tra rằng việc chạy một chương trình bị lỗi thông qua trình kiểm tra loại thực sự tạo ra một lỗi thống nhất! Một khi một cái gì đó là một String, nó sẽ biến mất mãi mãi và bất kỳ thông tin nào chứa trong đó đều mờ đục đối với trình biên dịch / kiểm tra, vì vậy cũng Either Stringkhông phải là tuyệt vời.

Cuối cùng tôi đóng gói loại này vào ExceptT, một máy biến áp đơn nguyên mới từ MTL. Điều này về cơ bản EitherTvà đi kèm với một loạt các chức năng tốt đẹp để ném và bắt lỗi theo cách thuần túy, dễ chịu.

Cuối cùng, điều đáng nói là Haskell có các cơ chế hỗ trợ xử lý các trường hợp ngoại lệ như các ngôn ngữ khác, ngoại trừ việc bắt một ngoại lệ tồn tại IO. Tôi biết một số người thích sử dụng những IOứng dụng này cho các ứng dụng nặng, nơi mọi thứ có thể có khả năng thất bại, nhưng không thường xuyên đến mức họ không muốn nghĩ về nó. Cho dù bạn sử dụng những ngoại lệ không tinh khiết này hay chỉ ExceptT Error IOthực sự là một vấn đề của hương vị. Cá nhân tôi chọn ExceptTvì tôi thích được nhắc nhở về cơ hội thất bại.


Như một bản tóm tắt,

  • Maybe - Tôi có thể thất bại theo một cách rõ ràng
  • Either CustomType - Tôi có thể thất bại, và tôi sẽ kể cho bạn nghe chuyện gì đã xảy ra
  • IO+ ngoại lệ - Đôi khi tôi thất bại. Kiểm tra tài liệu của tôi để xem những gì tôi ném khi tôi làm
  • error - Tôi ghét bạn quá người dùng

Câu trả lời này làm sáng tỏ rất nhiều cho tôi. Tôi đã tự đoán mình dựa trên thực tế rằng các chức năng tích hợp như headhoặc lastdường như sử dụng error, vì vậy tôi đã tự hỏi liệu đó có thực sự là một cách tốt để làm mọi thứ và tôi chỉ thiếu một cái gì đó. Câu trả lời này. :)
CmdrMoozy

5
@CmdrMoozy Vui mừng khi được giúp đỡ :) Thật không may là khúc dạo đầu có những thực hành tồi tệ như vậy. Đó là cách di sản: /
Daniel Gratzer

3
+1 Tóm tắt của bạn thật tuyệt vời
đệ quy.ninja

2

Tôi sẽ không tách 2 và 3 dựa trên số cách mà một cái gì đó có thể thất bại. Ngay khi bạn nghĩ chỉ có một cách có thể, một cách khác sẽ hiển thị khuôn mặt của nó. Thay vào đó, số liệu phải là "những người gọi của tôi quan tâm tại sao điều gì đó không thành công? Họ thực sự có thể làm gì về nó không?".

Ngoài ra, nó không rõ ràng ngay lập tức với tôi Either String SomeTypecó thể tạo ra một tình trạng lỗi. Tôi sẽ tạo một kiểu dữ liệu đại số đơn giản với các điều kiện với một tên mô tả nhiều hơn.

Việc bạn sử dụng phụ thuộc vào bản chất của vấn đề bạn gặp phải và thành ngữ của gói phần mềm bạn đang làm việc. Mặc dù tôi có xu hướng tránh # 1.


Trên thực tế, sử dụng Eithercho các lỗi là một mô hình rất nổi tiếng trong Haskell.
Rufflewind

1
@rufflewind - Eitherkhông phải là phần không rõ ràng, việc sử dụng Stringnhư là một lỗi thay vì một chuỗi thực tế.
Telastyn

Ah, tôi đọc sai ý định của bạn.
Rufflewind
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.