Có những quy tắc nào về chức năng a -> () được đánh giá trong Haskell?


12

Giống như tiêu đề nói: có gì đảm bảo cho đơn vị trả về hàm Haskell được đánh giá? Người ta sẽ nghĩ rằng không cần phải chạy bất kỳ loại đánh giá nào trong trường hợp như vậy, trình biên dịch có thể thay thế tất cả các cuộc gọi như vậy bằng một ()giá trị ngay lập tức trừ khi có yêu cầu rõ ràng về tính nghiêm ngặt, trong trường hợp đó, mã có thể phải quyết định liệu có nên quyết định hay không trở lại ()hoặc dưới cùng.
Tôi đã thử nghiệm điều này trong GHCi và có vẻ như điều ngược lại xảy ra, đó là, một chức năng như vậy dường như được đánh giá. Một ví dụ rất nguyên thủy sẽ là

f :: a -> ()
f _ = undefined

Đánh giá f 1ném một lỗi do sự hiện diện của undefined, vì vậy một số đánh giá chắc chắn xảy ra. Tuy nhiên, không rõ việc đánh giá đi sâu đến đâu; đôi khi nó dường như đi sâu đến mức cần thiết để đánh giá tất cả các lệnh gọi hàm trả về (). Thí dụ:

g :: [a] -> ()
g [] = ()
g (_:xs) = g xs

Mã này vòng lặp vô thời hạn nếu được trình bày với g (let x = 1:x in x). Nhưng sau đó

f :: a -> ()
f _ = undefined
h :: a -> ()
h _ = ()

có thể được sử dụng để hiển thị h (f 1)lợi nhuận đó (), vì vậy trong trường hợp này, không phải tất cả các biểu thức con có giá trị đơn vị đều được ước tính. Quy tắc chung ở đây là gì?

ETA: tất nhiên tôi biết về sự lười biếng. Tôi đang hỏi điều gì ngăn cản các nhà văn trình biên dịch làm cho trường hợp cụ thể này thậm chí còn lười hơn bình thường.

ETA2: tóm tắt các ví dụ: GHC dường như đối xử ()chính xác như bất kỳ loại nào khác, tức là như có một câu hỏi về giá trị thường xuyên nào sinh sống trong loại nên được trả về từ một hàm. Thực tế là chỉ có một giá trị như vậy dường như không được (ab) sử dụng bởi các thuật toán tối ưu hóa.

ETA3: khi tôi nói Haskell, ý tôi là Haskell-as-định-by-the-Report, không phải Haskell-the-H-in-GHC. Có vẻ như là một giả định không được chia sẻ rộng rãi như tôi tưởng tượng (chiếm 100% số độc giả), hoặc tôi có thể đã có thể đặt ra một câu hỏi rõ ràng hơn. Mặc dù vậy, tôi rất tiếc khi thay đổi tiêu đề của câu hỏi, vì ban đầu nó đã hỏi những gì đảm bảo có chức năng như vậy được gọi.

ETA4: có vẻ như câu hỏi này đã chạy đúng hướng của nó và tôi đang xem xét nó chưa được trả lời. (Tôi đang tìm kiếm một chức năng 'câu hỏi gần' nhưng chỉ tìm thấy 'trả lời câu hỏi của riêng bạn' và vì không thể trả lời được, tôi đã không đi theo con đường đó.) Không ai đưa ra bất cứ điều gì từ Báo cáo sẽ quyết định theo cách đó , điều mà tôi mong muốn diễn giải như một câu trả lời mạnh mẽ nhưng không chắc chắn 'không đảm bảo cho ngôn ngữ như vậy'. Tất cả những gì chúng ta biết là việc triển khai GHC hiện tại sẽ không bỏ qua việc đánh giá chức năng như vậy.

Tôi đã gặp phải vấn đề thực tế khi chuyển ứng dụng OCaml sang Haskell. Ứng dụng ban đầu có cấu trúc đệ quy lẫn nhau gồm nhiều loại và mã đã khai báo một số hàm được gọi là assert_structureN_is_correctN trong 1..6 hoặc 7, mỗi hàm trả về đơn vị nếu cấu trúc thực sự chính xác và ném ngoại lệ nếu không phải là cấu trúc . Ngoài ra, các hàm này gọi nhau khi chúng phân tách các điều kiện chính xác. Trong Haskell, điều này được xử lý tốt hơn bằng cách sử dụng Either Stringđơn nguyên, vì vậy tôi đã sao chép nó theo cách đó, nhưng câu hỏi như một vấn đề lý thuyết vẫn còn. Cảm ơn tất cả các đầu vào và trả lời.


1
Đây là sự lười biếng trong công việc. Trừ khi kết quả của hàm được yêu cầu (ví dụ: bằng cách khớp mẫu với hàm tạo), phần thân của hàm không được đánh giá. Để quan sát sự khác biệt, hãy thử so sánh h1::()->() ; h1 () = ()h2::()->() ; h2 _ = (). Chạy cả hai h1 (f 1)h2 (f 1), và thấy rằng chỉ có một yêu cầu đầu tiên (f 1).
chi

1
"Sự lười biếng dường như chỉ ra rằng nó được thay thế bằng () mà không có bất kỳ loại đánh giá nào xảy ra." Điều đó nghĩa là gì? f 1được "thay thế" bởi undefinedtrong mọi trường hợp.
oonomk

3
Một hàm ... -> ()có thể 1) chấm dứt và trả về (), 2) chấm dứt với lỗi ngoại lệ / thời gian chạy và không trả về bất cứ thứ gì, hoặc 3) phân kỳ (đệ quy vô hạn). GHC không tối ưu hóa mã chỉ giả sử 1) có thể xảy ra: nếu f 1được yêu cầu, nó không bỏ qua đánh giá và trả về (). Ngữ nghĩa của Haskell là đánh giá nó và xem điều gì xảy ra giữa 1,2,3.
chi

2
Thực sự không có gì đặc biệt về ()(loại hoặc giá trị) trong câu hỏi này. Tất cả các quan sát tương tự xảy ra nếu bạn thay thế () :: ()bằng, nói, 0 :: Intở mọi nơi. Đây là tất cả chỉ là hậu quả cũ nhàm chán của đánh giá lười biếng.
Daniel Wagner

2
không, "tránh", vv không phải là ngữ nghĩa Haskell. và có hai giá trị có thể của ()loại, ()undefined.
Will Ness

Câu trả lời:


10

Bạn dường như xuất phát từ giả định rằng loại ()chỉ có một giá trị có thể (), và do đó, mong đợi rằng bất kỳ lệnh gọi hàm nào trả về giá trị của loại ()sẽ tự động được giả sử để thực sự tạo ra giá trị ().

Đây không phải là cách Haskell hoạt động. Mỗi loại có thêm một giá trị trong Haskell, cụ thể là không có giá trị, lỗi, hay còn gọi là "đáy", được mã hóa bởi undefined. Do đó, một đánh giá đang thực sự diễn ra:

main = print (f 1)

tương đương với ngôn ngữ cốt lõi

main = _Case (f 1) _Of x -> print x   -- pseudocode illustration

hoặc thậm chí (*)

main = _Case (f 1) _Of x -> putStr "()"

và Core's _Caseđang buộc :

"Đánh giá một %case[biểu thức] buộc việc đánh giá biểu thức đang được kiểm tra (bộ lọc Scrutinee). Giá trị của bộ kiểm tra bị ràng buộc với biến theo %oftừ khóa, ...".

Giá trị buộc phải yếu đầu bình thường. Đây là một phần của định nghĩa ngôn ngữ.

Haskell không phải là một ngôn ngữ lập trình khai báo .


(*) print x = putStr (show x)show () = "()", do đó, showcuộc gọi có thể được tổng hợp hoàn toàn.

Giá trị thực sự được biết trước là ()và thậm chí giá trị show ()được biết trước là "()". Tuy nhiên, ngữ nghĩa Haskell được chấp nhận yêu cầu rằng giá trị của (f 1)bị buộc phải yếu ở dạng bình thường trước khi tiến hành in chuỗi đã biết trước "()".


chỉnh sửa: Xem xét concat (repeat []). Nó nên là [], hay nó nên là một vòng lặp vô hạn?

Câu trả lời của "ngôn ngữ khai báo" này có lẽ là nhất []. Câu trả lời của Haskell là, vòng lặp vô hạn .

Lười biếng "lập trình khai báo của người nghèo", nhưng nó vẫn không phải là điều thực sự .

chỉnh sửa2 : print $ h (f 1) == _Case (h (f 1)) _Of () -> print ()và chỉ hbị ép buộc, không f; và để đưa ra câu trả lời của nóh không phải ép buộc bất cứ điều gì, theo định nghĩa của nó h _ = ().

nhận xét chia tay: Sự lười biếng có thể có lý do nhưng đó không phải là định nghĩa của nó. Lười biếng là những gì nó được. Nó được định nghĩa là tất cả các giá trị ban đầu là thunks buộc phải WHNF theo các yêu cầu đến từ main. Nếu nó giúp tránh đáy trong một trường hợp cụ thể theo trường hợp cụ thể của nó, thì nó làm. Nếu không, không. Đó là tất cả.

Nó giúp tự thực hiện nó, bằng ngôn ngữ yêu thích của bạn, để cảm nhận về nó. Nhưng chúng ta cũng có thể theo dõi đánh giá của bất kỳ biểu thức nào bằng cách đặt tên cẩn thận cho tất cả các giá trị tạm thời khi chúng ra đời.


Đi theo Báo cáo , chúng tôi có

f :: a -> ()
f = \_ -> (undefined :: ())

sau đó

print (f 1)
 = print ((\ _ ->  undefined :: ()) 1)
 = print          (undefined :: ())
 = putStrLn (show (undefined :: ()))

và với

instance Show () where
    show :: () -> String
    show x = case x of () -> "()"

nó tiếp tục

 = putStrLn (case (undefined :: ()) of () -> "()")

Bây giờ, phần 3.17.3 Ngữ nghĩa chính thức của Kết hợp mẫu của Báo cáo cho biết

Các ngữ nghĩa của casebiểu thức [được đưa ra] trong Hình 3.1 .33.3. Bất kỳ triển khai nào cũng phải hành xử sao cho các danh tính này giữ [...].

và trường hợp (r)trong hình 3.2 trạng thái

(r)     case  of { K x1  xn -> e; _ -> e } =  
        where K is a data constructor of arity n 

() là hàm tạo dữ liệu của arity 0, vì vậy nó giống như

(r)     case  of { () -> e; _ -> e } =  

và kết quả tổng thể của đánh giá là như vậy .


2
Tôi thích lời giải thích của bạn. Nó rõ ràng và đơn giản.
mũi tên

@DanielWagner Tôi thực sự đã nghĩ caseđến Core, và đã bỏ qua lỗ hổng. :) Tôi đã chỉnh sửa để đề cập đến Core.
Will Ness

1
Sẽ không buộc phải được showviện dẫn bởi print? Một cái gì đó nhưshow x = case x of () -> "()"
user253751

1
Tôi đề cập đến casetrong Core, không phải trong chính Haskell. Haskell được dịch sang Core, có một lực lượng cưỡng bức case, AFAIK. Bạn đúng rằng casetrong Haskell không bị ép buộc bởi chính nó. Tôi có thể viết một cái gì đó bằng Scheme hoặc ML (nếu tôi có thể viết ML), hoặc mã giả.
Will Ness

1
Câu trả lời có thẩm quyền cho tất cả những điều này có lẽ nằm ở đâu đó trong Báo cáo. Tất cả những gì tôi biết là không có "tối ưu hóa" đang diễn ra ở đây và "giá trị thường xuyên" không phải là một thuật ngữ có ý nghĩa trong bối cảnh này. Bất cứ điều gì bị ép buộc, bị ép buộc. printlực lượng càng nhiều càng cần thiết để in. Nó không nhìn vào loại, các loại đã biến mất, bị xóa, vào thời điểm chương trình chạy, chương trình in chính xác đã được chọn và biên dịch, theo loại, tại thời điểm biên dịch; chương trình con đó vẫn sẽ buộc giá trị đầu vào của nó thành WHNF trong thời gian chạy và nếu nó không được xác định, nó sẽ gây ra lỗi.
Will Ness
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.