为什么第一个Haskell函数无法处理无限列表,而第二个代码段成功处理无限列表?
我有两个Haskell函数,这两个函数看起来都非常相似。但是第一种方法在无限列表中失败,第二种方法在无限列表中成功。我已经花了几个小时试图弄清楚这到底是为什么,但没有用 这两个片段都是Prelude中“words”函数的重新实现。这两种方法都可以很好地处理有限列表 以下是不处理无限列表的版本:为什么第一个Haskell函数无法处理无限列表,而第二个代码段成功处理无限列表?,haskell,functional-programming,fold,Haskell,Functional Programming,Fold,我有两个Haskell函数,这两个函数看起来都非常相似。但是第一种方法在无限列表中失败,第二种方法在无限列表中成功。我已经花了几个小时试图弄清楚这到底是为什么,但没有用 这两个片段都是Prelude中“words”函数的重新实现。这两种方法都可以很好地处理有限列表 以下是不处理无限列表的版本: myWords_FailsOnInfiniteList :: String -> [String] myWords_FailsOnInfiniteList string = foldr step [
myWords_FailsOnInfiniteList :: String -> [String]
myWords_FailsOnInfiniteList string = foldr step [] (dropWhile charIsSpace string)
where
step space ([]:xs) | charIsSpace space = []:xs
step space (x:xs) | charIsSpace space = []:x:xs
step space [] | charIsSpace space = []
step char (x:xs) = (char : x) : xs
step char [] = [[char]]
myWords_anotherReader :: String -> [String]
myWords_anotherReader xs = foldr step [""] xs
where
step x result | not . charIsSpace $ x = [x:(head result)]++tail result
| otherwise = []:result
以下是处理无限列表的版本:
myWords_FailsOnInfiniteList :: String -> [String]
myWords_FailsOnInfiniteList string = foldr step [] (dropWhile charIsSpace string)
where
step space ([]:xs) | charIsSpace space = []:xs
step space (x:xs) | charIsSpace space = []:x:xs
step space [] | charIsSpace space = []
step char (x:xs) = (char : x) : xs
step char [] = [[char]]
myWords_anotherReader :: String -> [String]
myWords_anotherReader xs = foldr step [""] xs
where
step x result | not . charIsSpace $ x = [x:(head result)]++tail result
| otherwise = []:result
注意:“charIsSpace”只是对Char.isSpace的重命名
下面的解释器会话说明第一个解释器在无限列表中失败,而第二个解释器成功
*Main> take 5 (myWords_FailsOnInfiniteList (cycle "why "))
*** Exception: stack overflow
*Main> take 5 (myWords_anotherReader (cycle "why "))
["why","why","why","why","why"]
编辑:感谢下面的回复,我相信我现在明白了。以下是我的结论和修订后的代码:
结论:
myWords :: String -> [String]
myWords string = foldr step [""] (dropWhile charIsSpace string)
where
step space acc | charIsSpace space = "":acc
step char (x:xs) = (char:x):xs
step _ [] = error "this should be impossible"
myWords :: String -> [String]
myWords string = foldr step [""] (dropWhile charIsSpace string)
where
step space acc | charIsSpace space = "":acc
step char (x:xs) = (char:x):xs
step _ [] = error "this should be impossible"
这对于无限列表是正确的。注意,没有头部、尾部或(++)操作符
现在有一个重要的警告:
当我第一次编写更正后的代码时,我没有第三个等式,它与“step[]”匹配。因此,我收到了关于非穷举模式匹配的警告。显然,避免这种警告是个好主意
但我想我会有问题的。我已经在上面提到,将第二个arg与[]进行模式匹配是不合适的。但我必须这样做才能摆脱警告
然而,当我添加“步骤[]方程”时,一切都很好!无限列表仍然没有问题。为什么?
因为修正代码中的第三个等式永远无法达到强>
事实上,考虑下面的版本。它与正确的代码完全相同,只是我将空列表的模式移到了其他模式之上:
myWords_brokenAgain :: String -> [String]
myWords_brokenAgain string = foldr step [""] (dropWhile charIsSpace string)
where
step _ [] = error "this should be impossible"
step space acc | charIsSpace space = "":acc
step char (x:xs) = (char:x):xs
我们回到堆栈溢出,因为调用步骤时发生的第一件事是解释器检查等式1是否匹配。为此,必须查看第二个arg是否为[]。为此,它必须计算第二个arg
将方程式向下移动到其他方程式下方可确保从不尝试第三个方程式,因为第一个或第二个模式始终匹配。第三个等式只是为了省去非穷举模式警告
这是一次很好的学习经历。感谢大家的帮助。第二个版本在开始生成自己的部分答案之前不会实际评估
结果。第一个版本通过模式匹配立即计算结果
这些无限列表的关键在于,在开始要求列表元素之前,您必须生成一些内容,以便输出总是“领先”于输入
(我觉得这个解释不是很清楚,但这是我能做的最好的解释。)试着用手展开表达式:
take 5 (myWords_FailsOnInfiniteList (cycle "why "))
take 5 (foldr step [] (dropWhile charIsSpace (cycle "why ")))
take 5 (foldr step [] (dropWhile charIsSpace ("why " ++ cycle "why ")))
take 5 (foldr step [] ("why " ++ cycle "why "))
take 5 (step 'w' (foldr step [] ("hy " ++ cycle "why ")))
take 5 (step 'w' (step 'h' (foldr step [] ("y " ++ cycle "why "))))
下一个扩展是什么?您应该看到,为了匹配步骤
,您需要知道它是否为空列表。为了找到答案,你必须对它进行评估,至少一点。但是第二个术语恰好是一个foldr
的缩减,它是由模式匹配所针对的函数来实现的。换句话说,step函数在不调用自身的情况下无法查看其参数,因此存在无限递归
将其与第二个功能的扩展进行对比:
myWords_anotherReader (cycle "why ")
foldr step [""] (cycle "why ")
foldr step [""] ("why " ++ cycle "why ")
step 'w' (foldr step [""] ("hy " ++ cycle "why ")
let result = foldr step [""] ("hy " ++ cycle "why ") in
['w':(head result)] ++ tail result
let result = step 'h' (foldr step [""] ("y " ++ cycle "why ") in
['w':(head result)] ++ tail result
您可能会看到,这种扩展将一直持续到到达某个空间为止。一旦到达一个空格,“head result”将获得一个值,您将生成答案的第一个元素
我怀疑第二个函数将为不包含任何空格的无限字符串溢出。你能理解为什么吗?其他人已经指出了问题所在,即步骤总是在生成任何输出之前计算其第二个参数,但其第二个参数
myWords :: String -> [String]
myWords string = foldr step [""] (dropWhile charIsSpace string)
where
step space acc | charIsSpace space = "":acc
step char (x:xs) = (char:x):xs
step _ [] = error "this should be impossible"
\n -> tail $ myWords $ replicate n 'a' ++ " b"
\n -> head $ head $ myWords $ replicate n 'a' ++ " b"
myWords :: String -> [String]
myWords = foldr step [""] . dropWhile isSpace
where
step space acc | isSpace space = "":acc
step char ~(x:xs) = (char:x):xs