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

dpa*_*tru 8 haskell lazy-sequences

为什么我<<loop>>在这里得到一个无限循环 ( ) 运行时错误?

文件反馈.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>>
Run Code Online (Sandbox Code Playgroud)
$ 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>>
Run Code Online (Sandbox Code Playgroud)

K. *_*uhr 7

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

merge (a, b) ~(as, bs) = (a:as, b+bs)
Run Code Online (Sandbox Code Playgroud)

to10和之间行为差异的原因to10plusto10可以返回列表的第一个元素而无需评估to10 xs,因此无需检查xs

相反,在它可以返回任何东西之前,to10plus必须成功调用merge参数(x, 1)to10plus xs。要使此调用成功,to10plus xs必须进行足够远的评估以确保它与(as, bs)的定义中使用的模式相匹配merge,但该评估需要检查 的元素xs,而这些元素尚不可用。

你也可以通过to10plus稍微不同的定义来避免这个问题:

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

在这里,to10plus可以提供第一个元素x的元组的第一部分,但不尝试评估as,所以没有尝试模式匹配to10plus xs(as,bs)where子句。一个let子句会做同样的事情:

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

正如@luqui 指出的那样,这与letwhere语句进行模式匹配的时间不同:

let (a,b) = expr in body
-- OR --
body where (a,b) = expr
Run Code Online (Sandbox Code Playgroud)

case语句/函数定义:

case expr of (a,b) -> body
-- OR --
f (a,b) = body  -- AND THEN EVALUATING: -- f expr
Run Code Online (Sandbox Code Playgroud)

letwhere语句懒惰地匹配的模式,这意味着expr不匹配于图案(a,b)直至ab在被评估body。相比之下,对于case语句,在甚至检查之前立即expr匹配。并且给定上述 for 的定义,参数 to将在表达式被评估后立即匹配,而无需等到或函数中需要。以下是一些工作示例来说明:(a,b)bodyff(a,b)f exprabbody

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
Run Code Online (Sandbox Code Playgroud)

添加会~更改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"
Run Code Online (Sandbox Code Playgroud)

  • 关键区别远非不言而喻,可能应该说明一下:“let”和“where”中的模式匹配总是惰性的,但参数的模式默认为严格的,除非使用“~”变得惰性。 (3认同)