2015-08-08 14 views
8

Tôi hiện đang học Haskell. Một trong những động lực tại sao tôi chọn ngôn ngữ này là viết phần mềm với mức độ mạnh mẽ rất cao, tức là các hàm được xác định đầy đủ, xác định toán học và không bao giờ bị lỗi hoặc tạo ra lỗi. Tôi không có nghĩa là thất bại gây ra bởi các vị từ của hệ thống ("hệ thống ra khỏi bộ nhớ", "máy tính trên lửa" vv), những người không phải là thú vị và có thể chỉ đơn giản là sụp đổ toàn bộ quá trình. Tôi cũng không có nghĩa là hành vi sai trái do khai báo không hợp lệ (pi = 4). Thay vào đó tôi tham khảo các lỗi gây ra bởi các trạng thái sai lầm mà tôi muốn loại bỏ bằng cách làm cho các trạng thái đó không thể đại diện và không thể compilable (trong một số hàm) thông qua gõ tĩnh nghiêm ngặt. Trong tâm trí của tôi, tôi gọi những hàm này là "thuần khiết" và nhận ra rằng hệ thống kiểu mạnh mẽ sẽ cho phép tôi thực hiện điều này. Tuy nhiên, Haskell không định nghĩa "thuần túy" theo cách này và cho phép các chương trình gặp sự cố thông qua error trong mọi ngữ cảnh.Mạnh mẽ haskell mà không có lỗi

Why is catching an exception non-pure, but throwing an exception is pure?

Điều này hoàn toàn có thể chấp nhận được và không lạ chút nào. Điều đáng thất vọng tuy nhiên là Haskell không xuất hiện để cung cấp một số chức năng để cấm định nghĩa chức năng có thể dẫn đến các chi nhánh bằng cách sử dụng error.

Dưới đây là một ví dụ contrived tại sao tôi tìm thấy điều này đáng thất vọng:

module Main where 
import Data.Maybe 

data Fruit = Apple | Banana | Orange Int | Peach 
    deriving(Show) 

readFruit :: String -> Maybe Fruit 
readFruit x = 
    case x of 
     "apple" -> Just Apple 
     "banana" -> Just Banana 
     "orange" -> Just (Orange 4) 
     "peach" -> Just Peach 
     _ -> Nothing 

showFruit :: Fruit -> String 
showFruit Apple = "An apple" 
showFruit Banana = "A Banana" 
showFruit (Orange x) = show x ++ " oranges" 

printFruit :: Maybe Fruit -> String 
printFruit x = showFruit $ fromJust x 

main :: IO() 
main = do 
    line <- getLine 
    let fruit = readFruit line 
    putStrLn $ printFruit fruit 
    main 

Hãy nói rằng tôi hoang tưởng rằng các chức năng tinh khiết readFruitprintFruit thực sự không thất bại do tiểu bang unhanded. Bạn có thể tưởng tượng rằng mã là để tung ra một tên lửa đầy đủ các phi hành gia mà trong một thói quen hoàn toàn quan trọng cần phải tuần tự hóa và unserialize các giá trị trái cây.

Nguy hiểm đầu tiên là tự nhiên mà chúng tôi đã phạm sai lầm trong kết hợp mẫu của chúng tôi vì điều này cho chúng ta những trạng thái sai lầm đáng sợ không thể xử lý được. Rất may Haskell cung cấp được xây dựng theo những cách để ngăn chặn những người, chúng tôi chỉ đơn giản là biên dịch chương trình của chúng tôi với -Wall trong đó bao gồm -fwarn-incomplete-patterns và AHA:

src/Main.hs:17:1: Warning: 
    Pattern match(es) are non-exhaustive 
    In an equation for ‘showFruit’: Patterns not matched: Peach 

Chúng tôi quên serialize trái cây Peach và showFruit sẽ ném một lỗi. Đó là một sửa chữa dễ dàng, chúng tôi chỉ cần thêm:

showFruit Peach = "A peach" 

Chương trình hiện đang biên dịch mà không có cảnh báo, nguy hiểm bị đảo ngược! Chúng tôi khởi động tên lửa nhưng đột nhiên chương trình bị treo với:

Maybe.fromJust: Nothing 

Các tên lửa được cam chịu và truy cập các đại dương gây ra bởi dòng bị lỗi sau:

printFruit x = showFruit $ fromJust x 

Về cơ bản fromJust có một chi nhánh nơi mà nó đặt ra một Error vì vậy chúng tôi thậm chí không muốn chương trình biên dịch nếu chúng tôi cố gắng sử dụng nó từ printFruit hoàn toàn phải là "siêu" thuần túy. Chúng ta có thể cố định ví dụ bằng cách thay thế phù hợp với:

printFruit x = maybe "Unknown fruit!" (\y -> showFruit y) x 

tôi thấy nó lạ mà Haskell quyết định để thực hiện gõ nghiêm ngặt và đầy đủ phát hiện mô hình, tất cả trong việc theo đuổi nobel ngăn ngừa trạng thái không hợp lệ từ việc biểu diễn nhưng thác chỉ ở phía trước của dòng kết thúc bằng cách không cho các lập trình viên một cách để phát hiện các chi nhánh đến error khi chúng không được phép.Theo một nghĩa nào đó, điều này làm cho Haskell kém mạnh mẽ hơn khi Java buộc bạn phải khai báo các ngoại lệ mà các chức năng của bạn được phép nâng lên.

Cách nhỏ nhất để thực hiện điều này sẽ đơn giản là không xác định error theo cách nào đó, cục bộ cho hàm và hàm bất kỳ được sử dụng bởi phương trình của nó. Tuy nhiên điều này dường như không thể.

The wiki page about errors vs exceptions đề cập đến một tiện ích mở rộng được gọi là "Kiểm tra tĩnh mở rộng" cho mục đích này thông qua hợp đồng nhưng nó chỉ dẫn đến một liên kết bị hỏng.

Về cơ bản, nó tóm tắt: Làm cách nào để chương trình ở trên không biên dịch được vì nó sử dụng fromJust? Tất cả các ý tưởng, đề xuất và giải pháp đều được chào đón.

+1

(1) Tôi tin rằng [đây là những giấy tờ bạn đang tìm kiếm] (http://research.microsoft.com/~simonpj/papers/verify/index.htm). (2) Lưu ý rằng 'printFruit' là không cần thiết. Nếu một người dùng muốn hiển thị một 'Có lẽ trái cây ', cô ấy có thể sử dụng' fmap showFruit' và sau đó unwrap 'Maybe' một cách hợp lý. Điều đó tất nhiên không trả lời câu hỏi của bạn (sẽ tốt nếu giữ người dùng tránh xa 'fromJust'!), Do đó hãy tiếp tục :) – duplode

+3

Tôi muốn Haskell có một bộ kiểm tra tổng thể. Tôi thậm chí sẽ hài lòng với một kiểm tra không có ngoại lệ kiểm tra không đầy đủ và sử dụng các chức năng một phần, nhưng không kiểm tra cho các vòng vô hạn. Tôi cũng muốn nâng cao các loại cảnh báo này thành lỗi, để tôi không thể dễ dàng bỏ qua chúng. Điều này có thể sẽ không đòi hỏi một lượng lớn nỗ lực để thực hiện trong GHC. – chi

+2

Bạn đã xem Idris chưa? Nó có thể là ngôn ngữ bạn muốn. – user3237465

Trả lời

1

Câu trả lời là tôi muốn là một mức độ toàn bộ kiểm tra mà Haskell không may không cung cấp (chưa?).

Hoặc tôi muốn các loại phụ thuộc (ví dụ: Idris) hoặc trình xác minh tĩnh (ví dụ: Liquid Haskell) hoặc phát hiện lint cú pháp (ví dụ: hlint).

Tôi thực sự đang tìm kiếm Idris bây giờ, có vẻ như đó là một ngôn ngữ tuyệt vời. Đây là số talk by the founder of Idris mà tôi có thể khuyên bạn nên xem.

Tín dụng cho những câu trả lời này sẽ chuyển đến @duplode, @chi, @ user3237465 và @luqui.

1

Khiếu nại chính của bạn là về fromJust, nhưng về cơ bản nó chống lại mục tiêu của bạn là gì, tại sao bạn sử dụng nó?

Hãy nhớ rằng lý do lớn nhất cho error là không phải mọi thứ đều có thể được xác định theo cách mà hệ thống kiểu có thể đảm bảo, vì vậy việc có "Điều này không thể xảy ra, tin tưởng tôi" có ý nghĩa. fromJust xuất phát từ suy nghĩ này của "đôi khi tôi biết rõ hơn trình biên dịch". Nếu bạn không đồng ý với suy nghĩ đó thì không sử dụng nó. Ngoài ra, đừng lạm dụng nó như ví dụ này.

EDIT:

Mở rộng trên quan điểm của tôi, suy nghĩ về cách bạn sẽ thực hiện những gì bạn đang yêu cầu (cảnh báo về việc sử dụng các chức năng một phần). Cảnh báo sẽ áp dụng ở đâu trong đoạn mã sau?

a = fromJust $ Just 1 
b = Just 2 
c = fromJust $ c 
d = fromJust 
e = d $ Just 3 
f = d b 

Tất cả những điều này là tĩnh không phải là một phần sau khi tất cả. (Xin lỗi nếu cú ​​pháp tiếp theo của này là tắt, nó là muộn)

g x = fromJust x + 1 
h x = g x + 1 
i = h $ Just 3 
j = h $ Nothing 
k x y = if x > 0 then fromJust y else x 
l = k 1 $ Just 2 
m = k (-1) $ Just 3 
n = k (-1) $ Nothing 

i đây là một lần nữa an toàn, nhưng không phải là j. Phương thức nào sẽ trả lại cảnh báo? Chúng ta làm gì khi một số hoặc tất cả các phương thức này được định nghĩa bên ngoài chức năng của chúng ta. Bạn làm gì trong trường hợp của k nơi nó có điều kiện một phần? Một số hoặc tất cả các chức năng này có bị lỗi không?

Bằng cách làm cho trình biên dịch thực hiện cuộc gọi này, bạn đang làm xáo trộn rất nhiều vấn đề phức tạp. Đặc biệt là khi bạn xem xét lựa chọn thư viện cẩn thận tránh nó.

Trong cả hai trường hợp, giải pháp IMO đối với các loại vấn đề này là sử dụng một công cụ sau hoặc trong quá trình biên dịch để tìm kiếm các vấn đề thay vì sửa đổi trình biên dịch. Một công cụ như vậy có thể dễ dàng hiểu "mã của bạn" so với "mã của chúng" và xác định tốt hơn nơi tốt nhất để cảnh báo bạn khi sử dụng không phù hợp. Bạn cũng có thể điều chỉnh nó mà không cần phải thêm một loạt chú thích vào tệp nguồn của mình để tránh các kết quả dương tính giả. Tôi không biết một công cụ nào hoặc tôi sẽ đề nghị một công cụ.

+2

Bạn đang tiếp cận câu hỏi của tôi từ góc độ sai. Ví dụ này được đưa ra để làm nổi bật cách một lập trình viên có thể giới thiệu một đường dẫn lỗi do nhầm lẫn. Sai lầm cũng có thể tinh vi hơn như cho phép một nhánh chia cho 0 là có thể. Giả định "tốt, chỉ không phạm sai lầm" làm cho hệ thống kiểu bản thân phần lớn là vô nghĩa. Ngoài ra bạn đang thừa nhận bản thân rằng chức năng này nguy hiểm hơn, nó sẽ không hữu ích nếu các cuộc gọi đến các chức năng nguy hiểm như vậy bằng cách nào đó có thể được ngăn chặn bởi trình biên dịch? –

+0

@HannesLandeholm: Tôi sẽ mở rộng câu trả lời của mình, nhưng đây không phải là trường hợp "không phạm sai lầm" là "không sử dụng các công cụ không đồng ý với mô hình của bạn". Nếu 'fromJust' và ilk của nó có thể phá vỡ guarentees thì lập luận của bạn sẽ có giá trị, nhưng' _ | _' luôn là thứ có thể leo lên. – Guvante

+2

Gần đây tôi đã bắt đầu làm việc trên một codebase 7 triệu dòng (không phải trong Haskell), vì vậy tôi đặt trên ống kính đó khi đọc câu hỏi của OP. "Không sử dụng X" không thực sự là một giải pháp chấp nhận được ở một nơi rộng lớn như vậy. Nó sẽ là điều cần thiết để có một công cụ kiểm tra cho các trường hợp như vậy. FWIW ['hlint'] (http://community.haskell.org/~ndm/hlint/) sẽ làm điều này nếu tôi nhớ đúng. – luqui

4

Haskell cho phép đệ quy chung tùy ý, vì vậy nó không phải là tất cả các chương trình Haskell nhất thiết phải là tổng số, nếu chỉ có error trong các hình thức khác nhau của nó đã bị xóa. Tức là, bạn chỉ có thể xác định error a = error a để xử lý mọi trường hợp bạn không muốn xử lý anyways. Lỗi thời gian chạy chỉ hữu ích hơn vòng lặp vô hạn.

Bạn không nên nghĩ đến error tương tự như ngoại lệ Java nhiều loại vườn. error là lỗi xác nhận đại diện cho lỗi lập trình và chức năng như fromJust có thể gọi error là xác nhận.Bạn không nên cố gắng nắm bắt ngoại lệ được tạo ra bởi error ngoại trừ trong trường hợp đặc biệt như máy chủ phải tiếp tục chạy ngay cả khi trình xử lý yêu cầu gặp phải lỗi lập trình.

+0

Tôi sẽ không nói rằng thuật ngữ "khẳng định" là hiển nhiên trong trường hợp này vì nó thường được kết hợp với cấu trúc tài liệu/gỡ lỗi có thể được loại bỏ một cách an toàn vì lý do hiệu suất ở các ngôn ngữ khác và không có ý nghĩa gì về logic chức năng của chương trình . Thay vào đó, một lỗi trong Haskell thì tương tự như lỗi truyền động trong các ngôn ngữ khác. Khi các chức năng tùy ý có thể ném chúng, bạn đang đi bộ một bãi mìn nếu bạn đang viết phần mềm mạnh mẽ và thậm chí không trình biên dịch có thể giúp bạn. Một cuộc gọi API sai và bạn phải chịu số phận. –

+2

@hanneslandeholm, bạn nên xem xét các ngôn ngữ như Coq và Agda. Haskell không được thiết kế cho những gì bạn muốn, mặc dù nó cung cấp một số công cụ tốt để viết mã an toàn và chính xác. Không ai đã quản lý, cho đến nay, để viết một ngôn ngữ tiêu chuẩn hóa, đánh máy một cách phụ thuộc, cũng thích hợp cho hầu hết các chương trình thực tế. Haskell là một sự thỏa hiệp. – dfeuer

+1

@dfeuer, tại sao Idris không phù hợp với lập trình thực tế? Nó có hệ thống hiệu ứng thích hợp, biên dịch sang C, thực hiện các tối ưu hóa khác nhau, có rất nhiều đường cú pháp, lỗ loại thích hợp (tốt, Agda là tốt hơn), chiến thuật và các công cụ khá thiết thực khác.So sánh điều này với máy biến áp đơn cực siêu xấu xí và viện [hasochism] (https://personal.cis.strath.ac.uk/conor.mcbride/pub/hasochism.pdf). Một ngôn ngữ lập trình được đánh máy phụ thuộc thực tế đã tồn tại, nó chỉ có hệ sinh thái thuần túy. – user3237465

Các vấn đề liên quan