Khi nào tôi có thể dựa vào Haskell để đọc một danh sách một cách lười biếng?


8

Tại sao tôi nhận được một <<loop>>lỗi thời gian chạy vòng lặp vô hạn ( ) ở đây?

phản hồi tập tin.hs:

plus1 :: [Int]->[Int] -- add 1 to input stream
plus1 [] = []
plus1 (x:xs) = (x+1): plus1 xs

to10 :: [Int] -> [Int] -- stop the input stream when it gets to 10
to10 (x:xs) | x < 10 = x : to10 xs
            | otherwise = []

to10plus :: [Int] -> ([Int], Int) -- like to10 but also return the count
to10plus (x:xs) | x < 10 = (x, 1) `merge` (to10plus xs)
            | otherwise = ([], 0)
  where merge (a, b) (as, bs) = (a:as, b+bs)

main = do
  let out = plus1 $ 1: to10 out
  putStrLn $ show out -- gives [2,3,4,5,6,7,8,9,10]


  let out = plus1 $ 1: out2
      out2 = to10 out
  putStrLn $ show out -- same as above

  let out = plus1 $ 1: out2
      (out2, count) = to10plus out
  putStrLn $ show (out, count) -- expect ([2,3,4,5,6,7,8,9,10], 8) 
                               -- but get runtime error: <<loop>>
$ ghc feedback.hs 
[1 of 1] Compiling Main             ( feedback.hs, feedback.o )
Linking feedback ...
$ ./feedback
[2,3,4,5,6,7,8,9,10]
[2,3,4,5,6,7,8,9,10]
feedback: <<loop>>

Câu trả lời:


7

Bạn có thể khắc phục to10plusbằng cách sử dụng kết quả khớp không thể bác bỏ (nghĩa là ~tiền tố) trong định nghĩa của bạn về merge:

merge (a, b) ~(as, bs) = (a:as, b+bs)

Lý do cho sự khác biệt trong hành vi giữa to10to10plusto10có thể trả về yếu tố đầu tiên của danh sách mà không phải đánh giá to10 xsvà do đó mà không cần kiểm tra xs.

Ngược lại, trước khi nó có thể trả về bất cứ thứ gì, to10plusphải gọi thành công mergevới các đối số (x, 1)to10plus xs. Để cuộc gọi này thành công, to10plus xsphải được đánh giá đủ xa để đảm bảo nó phù hợp với mẫu (as, bs)được sử dụng trong định nghĩa merge, nhưng đánh giá đó yêu cầu kiểm tra các yếu tố của xs, chưa có sẵn.

Bạn cũng có thể tránh được vấn đề bằng cách định nghĩa to10plusmột chút khác biệt:

to10plus (x:xs) | x < 10 = (x:as,1+bs)
                | otherwise = ([], 0)
  where (as,bs) = to10plus xs

Ở đây, to10pluscó thể cung cấp các yếu tố đầu tiên xcủa phần đầu tiên của tuple mà không cần cố gắng để đánh giá as, và như vậy mà không cố gắng để phù hợp với mô hình to10plus xsvới (as,bs)trong wherekhoản. Một letmệnh đề sẽ làm điều tương tự:

to10plus (x:xs) | x < 10 = let (as,bs) = to10plus xs in (x:as,1+bs)
                | otherwise = ([], 0)

Như @luqui chỉ ra, đây là một sự khác biệt về thời gian cho các kết quả khớp mẫu được thực hiện bởi letvà các wherecâu lệnh:

let (a,b) = expr in body
-- OR --
body where (a,b) = expr

so với casecác định nghĩa / định nghĩa hàm:

case expr of (a,b) -> body
-- OR --
f (a,b) = body  -- AND THEN EVALUATING: -- f expr

Các câu lệnh letvà các wherecâu lệnh khớp với các mẫu một cách lười biếng, có nghĩa exprlà không khớp với mẫu (a,b)cho đến khi ahoặc bđược đánh giá trong body. Ngược lại, đối với casetuyên bố, exprđược khớp với (a,b)ngay lập tức, trước khi bodythậm chí được kiểm tra. Và đưa ra định nghĩa trên cho f, đối số fsẽ được khớp với (a,b)ngay khi biểu thức f exprđược ước tính mà không cần đợi cho đến khi ahoặc bcần thiết trong hàm body. Dưới đây là một số ví dụ hoạt động để minh họa:

ex1 = let (a,b) = undefined in print "okay"
ex2 = print "also okay" where (a,b) = undefined
ex3 = case undefined of (a,b) -> print "not okay"
ex4 = f undefined
f (a,b) = print "also not okay"

main = do
  ex1   -- works
  ex2   -- works
  ex3   -- fails
  ex4   -- fails

Việc thêm ~thay đổi hành vi cho case/ định nghĩa hàm để việc khớp chỉ diễn ra khi ahoặc bcần thiết:

ex5 = case undefined of ~(a,b) -> print "works fine"
ex6 = g undefined
g ~(a,b) = print "also works fine"

ex7 = case undefined of ~(a,b) -> print $ "But trying " ++ show (a+b) ++ " would fail"

3
Sự khác biệt chính khác xa với hiển nhiên và có lẽ nên được nêu rõ: mẫu khớp với letwhereluôn lười biếng, nhưng mẫu cho các đối số mặc định là nghiêm ngặt trừ khi lười sử dụng ~.
luqui

Cảm ơn KA Buhr và luqui!
Daniel Patru
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.