我什么时候可以依靠 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)
to10
和 to10plus
之间行为不同的原因是 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 xs
在 where
子句中使用 (as,bs)
。 let
子句会做同样的事情:
to10plus (x:xs) | x < 10 = let (as,bs) = to10plus xs in (x:as,1+bs)
| otherwise = ([], 0)
正如@luqui 指出的那样,这是 let
和 where
语句进行模式匹配的时间差异:
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
let
和 where
语句延迟匹配模式,这意味着 expr
与模式 (a,b)
不匹配,直到 a
或 b
在 body
中计算。相反,对于 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
/ 函数定义的行为,因此只有在需要 a
或 b
时才会进行匹配:
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"
为什么会出现无限循环 (<<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)
to10
和 to10plus
之间行为不同的原因是 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 xs
在 where
子句中使用 (as,bs)
。 let
子句会做同样的事情:
to10plus (x:xs) | x < 10 = let (as,bs) = to10plus xs in (x:as,1+bs)
| otherwise = ([], 0)
正如@luqui 指出的那样,这是 let
和 where
语句进行模式匹配的时间差异:
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
let
和 where
语句延迟匹配模式,这意味着 expr
与模式 (a,b)
不匹配,直到 a
或 b
在 body
中计算。相反,对于 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
/ 函数定义的行为,因此只有在需要 a
或 b
时才会进行匹配:
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"