我什么时候可以依靠 Haskell 懒惰地阅读列表?

When can I rely on Haskell to read a list lazily?

为什么会出现无限循环 (<<loop>>) 运行时错误?

文件feedback.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>>

您可以通过在 merge 的定义中使用无可辩驳的匹配项(即 ~ 前缀)来修复 to10plus:

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

to10to10plus 之间行为不同的原因是 to10 可以 return 列表的第一个元素而不必评估 to10 xs 等不检查 xs.

相比之下,return 之前,to10plus 必须使用参数 (x, 1)to10plus xs 成功调用 merge。为了使此调用成功,必须对 to10plus xs 进行足够的评估以确保它与 merge 定义中使用的模式 (as, bs) 相匹配,但该评估需要检查 xs 的元素,尚不可用。

您也可以通过稍微不同地定义 to10plus 来避免该问题:

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

在这里,to10plus 可以提供元组第一部分的第一个元素 x 而无需尝试评估 as,因此无需尝试模式匹配 to10plus xswhere 子句中使用 (as,bs)let 子句会做同样的事情:

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

正如@luqui 指出的那样,这是 letwhere 语句进行模式匹配的时间差异:

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

case 语句/函数定义:

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

letwhere 语句延迟匹配模式,这意味着 expr 与模式 (a,b) 不匹配,直到 abbody 中计算。相反,对于 case 语句,expr 立即匹配到 (a,b),甚至在检查 body 之前。并且鉴于 f 的上述定义,一旦表达式 f expr 被评估,f 的参数将匹配到 (a,b),而不用等到 a 或函数 body 中需要 b。下面是一些工作示例来说明:

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

添加 ~ 会更改 case / 函数定义的行为,因此只有在需要 ab 时才会进行匹配:

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"