2011-10-09 45 views
27

Ý nghĩa của ngoại lệ trong Haskell là gì? Cách sử dụng duy nhất tôi thấy là đặt undefined hoặc error vào mã của tôi để ngăn các chương trình chạy. Nếu không, tôi xem xét lập trình với các ngoại lệ như một lỗ hổng thiết kế hợp lý. Nhưng Haskell có một mô-đun ngoại lệ tiên tiến Control.Exception được sử dụng bởi Prelude. Tôi đọc rằng lý do ngoại lệ trong C++ là để ngăn chặn rất nhiều "chức năng gọi, sau đó kiểm tra trạng thái" - dòng trong mã. Nhưng những thứ như vậy có thể được trừu tượng hóa trong Haskell. Lý do duy nhất tôi có thể thấy để xử lý ngoại lệ trong Haskell là với FFI, để xử lý ngoại lệ ngoại lệ, nhưng chỉ để sử dụng nội bộ trong một hàm Haskell gói cuộc gọi.Ý nghĩa của ngoại lệ Haskell

+0

Có nhiều lý do khiến bạn có thể bị ngoại lệ. Trong một hệ thống ngoại lệ ít xảy ra khi nào: bạn hết bộ nhớ? Hệ điều hành gửi tín hiệu? Bạn chia cho số không? Bạn đánh giá một phần chức năng ... và khám phá nó là một phần! Ngoại lệ không phải là một phần khá của bất kỳ ngôn ngữ nào tôi biết, nhưng để đối phó với thế giới thực, chúng là một phần cần thiết. –

+4

Có cách nào thực sự để trả lời câu hỏi này, hay nó chỉ là một câu trả lời? Nếu nó chỉ là một rant, có lẽ một bài đăng blog sẽ là một phương tiện thích hợp hơn. –

+1

Có, cho những điều như vậy có thể dừng chương trình, vì vậy chúng tôi có 'không xác định' và 'lỗi' như tôi đã nói. Chia cho 0 và đánh giá một hàm "bên ngoài miền của nó" là các lỗi thiết kế logic. Nhưng tôi không thấy ý nghĩa của việc sử dụng các ngoại lệ trong cơ chế của một chương trình (trong Haskell). – telephone

Trả lời

27

Theo ý kiến ​​khiêm tốn của tôi, ngoại lệ có nghĩa là "bạn đã phá vỡ hợp đồng của một hàm". Tôi không nói về hợp đồng loại, tôi đang nói về những thứ bạn thường thấy trong các bình luận.

-- The reciprocal is not defined for the value 0 
myRecip :: Fractional a => a -> a 
myRecip x | x == 0 = throw DivideByZero 
      | otherwise = 1/x 

Tất nhiên bạn có thể luôn cung cấp chức năng này là "an toàn" cách:

safeRecip :: Fractional a => a -> Maybe a 
safeRecip x | x == 0 = Nothing 
      | otherwise = Just $ 1/x 

Có lẽ chúng ta nên thậm chí trừu tượng mô hình này

restrictInput :: (a -> Bool) -> (a -> b) -> (a -> Maybe b) 
restrictInput pred f x = if pred x then Just (f x) else Nothing 

safeRecip' = restrictInput (/= 0) myRecip 

Bạn có thể tưởng tượng combinators tương tự cho việc sử dụng Either thay vì Maybe để báo cáo lỗi. Vì vậy, kể từ khi chúng tôi có khả năng xử lý những điều này với các chức năng thuần túy, tại sao phải bận tâm với mô hình ngoại lệ không tinh khiết? Vâng, hầu hết các Haskellers sẽ cho bạn biết để gắn bó với độ tinh khiết. Nhưng sự thật đen tối là, nó chỉ là một nỗi đau để thêm lớp bổ sung đó. Bạn có thể không còn viết

prop_recip x = x == (recip . recip) x 

Bởi vì bây giờ là kết quả của recip x là trong một Maybe. Có những điều hữu ích mà bạn có thể thực hiện với các chức năng (a -> a) mà bạn không thể làm nữa. Bây giờ bạn phải suy nghĩ về sáng tác của Maybes. Điều này, tất nhiên, là tầm thường nếu bạn cảm thấy thoải mái với monads:

prop_recip 0 = (safeRecip >=> safeRecip) 0 == Nothing 
prop_recip x = (safeRecip >=> safeRecip) x == Just x 

Nhưng trong đó có sự chà xát. Người mới thường biết bên cạnh không có gì khi nói đến thành phần monadic. Ủy ban Haskell, như nhiều người trên kênh #haskell irc sẽ nhanh chóng cho bạn biết *, đã đưa ra một số quyết định khá khả quan về thiết kế ngôn ngữ để phục vụ cho người mới. Chúng tôi muốn có thể nói "bạn không cần phải biết monads để bắt đầu làm những điều hữu ích trong Haskell". Và tôi thường đồng ý với tình cảm đó.

tl; dr Một vài câu trả lời nhanh cho câu hỏi: Ngoại lệ là gì?

  • một bẩn hack vì vậy chúng tôi không phải thực hiện chức năng của chúng tôi hoàn toàn an toàn
  • một "try/catch" cơ chế kiểm soát dòng chảy cho các đơn nguyên IO (IO là một bin tội lỗi, vậy tại sao không ném cố gắng/nắm bắt trong danh sách tội lỗi?)

Có thể có những giải thích khác.

Xem thêm Haskell Report > Basic Input/Output > Exception Handling in the IO Monad

* Tôi thực sự hỏi trên #haskell irc nếu họ chấp thuận của tuyên bố này. Câu trả lời duy nhất tôi nhận được là "wonky" :) vì vậy nó được chứng minh là đúng bởi sự vắng mặt của sự phản đối.


[Chỉnh sửa] Lưu ý rằng errorundefined được quy định tại các điều khoản của throw:

error :: [Char] -> a 
error s = throw (ErrorCall s) 

undefined :: a 
undefined = error "Prelude.undefined" 
+3

Sử dụng các ngoại lệ khác với 'lỗi' trong hàm đối ứng là những gì tôi xem là sử dụng sai các ngoại lệ. Hàm này sẽ không bao giờ được gọi với giá trị đó và chương trình sẽ không bao giờ cố gắng bắt ngoại lệ từ nó, nếu có một lỗ hổng hợp lý trong chương trình của chúng ta. Tôi thích giết chương trình trong những trường hợp như vậy với 'lỗi', để phát hiện lỗi. Nếu lỗi được xử lý âm thầm với xử lý ngoại lệ, nó có thể gây ra các lỗi khác sau này. – telephone

+3

@telephone sử dụng 'error' không phải là bất cứ điều gì huyền diệu: bạn có thể nắm bắt được ngoại lệ' ErrorCall' bị ném và xử lý nó âm thầm. 'let foo a = error" rawr "trong Control.Exception.Base.catch (foo()) (\ (ErrorCall msg) -> putStrLn msg)' in thông báo "rawr" –

+0

Làm rõ: Tôi đồng ý rằng xử lý các ngoại lệ âm thầm là khá tàn bạo. Tôi đã chỉ nói rằng 'error' là (về cơ bản) không có gì nhiều hơn một macro cho' (throw. ErrorCall) ', do đó tôi không đồng ý rằng việc sử dụng các ngoại lệ khác với 'error' là xấu. Bạn có thể giết chương trình bằng bất kỳ ngoại lệ nào của ol miễn là bạn không bắt được nó; 'ErrorCall' không khác nhau. Tuy nhiên, điều tốt đẹp về 'ErrorCall' (và chức năng thuận tiện' error') của nó là nó làm cho nó có thể khiến bạn chỉ cần viết thông báo lỗi của mình trong một String, chứ không phải là rắc rối khi tạo ra kiểu ngoại lệ của riêng bạn. –

1

Exceptions là một hình thức hợp pháp kiểm soát dòng chảy. Nó không rõ ràng với tôi tại sao, khi đưa ra một công cụ, các lập trình viên nhấn mạnh rằng nó là "chỉ cho" các trường hợp nhất định và loại trừ việc sử dụng có thể khác.

Ví dụ: nếu bạn đang thực hiện tính toán theo dõi ngược, bạn có thể sử dụng ngoại lệ để quay lại. Trong Haskell nó có lẽ sẽ phổ biến hơn để sử dụng danh sách monad cho điều này, nhưng trường hợp ngoại lệ là một cách hợp pháp để đi.

5

Chức năng "lỗi" là khi chức năng nhận đầu vào không hợp lệ hoặc khi nội dung nào đó xảy ra không được xảy ra (tức là lỗi). Trong ngắn hạn, gọi là "lỗi" đại diện cho một lỗi - hoặc trong người gọi hoặc callee.

Hằng số "không xác định" là nhiều hơn cho các giá trị không được sử dụng - thường là vì chúng sẽ được thay thế bằng thứ khác hoặc vì chúng là giá trị ảo được sử dụng để có loại cụ thể. (Nó thực sự được thực hiện như một cuộc gọi đến "lỗi".)

Vậy tại sao chúng ta có Control.Exception với tất cả sự thịnh hành của nó?

Về cơ bản, "vì các hoạt động I/O có thể ném ngoại lệ". Bạn có thể vui vẻ nói chuyện với một máy chủ FTP qua một ổ cắm TCP, và đột nhiên ngắt kết nối. Kết quả? Chương trình của bạn ném một ngoại lệ. Hoặc bạn có thể hết RAM hoặc đĩa có thể lấp đầy hoặc bất kỳ thứ gì.

Lưu ý rằng hầu như tất cả những điều này là không phải lỗi của bạn. Nếu bạn có thể dự đoán một điều cụ thể xảy ra, bạn nên sử dụng những thứ như MaybeEither để xử lý điều đó một cách thuần túy. (Ví dụ, nếu bạn định đảo ngược một ma trận, thì ma trận có thể không đảo ngược được, vì vậy bạn nên trả lại kết quả là Maybe Matrix.) Đối với những thứ bạn không thể đoán trước được (ví dụ, một số khác chương trình vừa xóa tập tin bạn đang cố gắng làm việc), ngoại lệ là cách.

Lưu ý rằng Control.Exception chứa nhiều nội dung để xử lý cũng như chỉ xác định nhiều loại khác nhau. Tôi không chăm sóc nếu mã tôi gọi đã làm điều gì đó không chính xác và do đó có lỗi; Tôi vẫn muốn có thể nói với khách hàng tôi vừa nói rằng kết nối sắp bị đóng, đăng nhập một mô tả vào một tệp nhật ký ở đâu đó và thực hiện các công cụ dọn dẹp khác, thay vì chỉ có chương trình của tôi đột ngột, bạn biết, dừng lại.