Cha*_*ers 11 haskell functional-programming fold
我有两个Haskell函数,这两个函数看起来和我非常相似.但是第一个FAILS反对无限列表,第二个反对无限列表成功.我一直在努力确定原因,但无济于事.
两个片段都是Prelude中"单词"功能的重新实现.两者都可以对抗有限列表.
这是不处理无限列表的版本:
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]]
Run Code Online (Sandbox Code Playgroud)
这是处理无限列表的版本:
myWords_anotherReader :: String -> [String]
myWords_anotherReader xs = foldr step [""] xs
where
step x result | not . charIsSpace $ x = [x:(head result)]++tail result
| otherwise = []:result
Run Code Online (Sandbox Code Playgroud)
注意:"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"]
Run Code Online (Sandbox Code Playgroud)
编辑:感谢下面的回复,我相信我现在明白了.以下是我的结论和修订后的代码:
结论:
所以,这是修改后的代码.我通常试图避免头部和尾部,仅仅因为它们是部分功能,而且因为我需要练习编写相应的模式匹配.
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"
Run Code Online (Sandbox Code Playgroud)
这正确地适用于无限列表.请注意,看不到头部,尾部或(++)操作员.
现在,一个重要的警告: 当我第一次写出更正的代码时,我没有第三个等式,它与"step _ []"相匹配.结果,我收到了关于非详尽模式匹配的警告.显然,避免这种警告是个好主意.
但我以为我会遇到问题.我上面已经提到,将第二个arg与[]进行模式匹配是不行的.但我必须这样做才能摆脱警告.
但是,当我添加"step _ []"等式时,一切都很好!无限列表仍然没有问题!.为什么?
因为修正后的代码中的第3个等式永远不会达到!
实际上,请考虑以下BROKEN版本.除了我已将空列表的模式移到其他模式之上之外,它确实是相同的正确代码:
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
Run Code Online (Sandbox Code Playgroud)
我们回到堆栈溢出,因为调用step时发生的第一件事是解释器检查方程式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 "))))
Run Code Online (Sandbox Code Playgroud)
下一次扩张是什么?你应该看到,为了模式匹配step,你需要知道它是否是空列表.为了找到答案,你必须至少对它进行评估.但是,第二个术语恰好是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
Run Code Online (Sandbox Code Playgroud)
您可能会看到此扩展将持续到达到空间.一旦达到空格,"头部结果"将获得一个值,您将产生答案的第一个元素.
我怀疑第二个函数将溢出无限字符串,不包含任何空格.你能明白为什么吗?
其他人已经指出了这个问题,即在生成任何输出之前,step总是会评估它的第二个参数,但是当foldr应用于无限列表时,它的第二个参数最终将取决于步骤的另一个调用的结果.
它不必以这种方式编写,但是你的第二个版本有点难看,因为它依赖于具有特定格式的步骤的初始参数,并且很难看出头/尾永远不会出错.(我甚至不能100%肯定他们不会!)
您应该做的是重构第一个版本,以便在至少某些情况下产生输出而不依赖于输入列表.特别是我们可以看到,当字符不是空格时,输出列表中始终至少有一个元素.因此,延迟第二个参数的模式匹配,直到产生第一个元素.字符是空格的情况仍然依赖于列表,但这很好,因为案例可以无限递归的唯一方法是传入无限的空格列表,在这种情况下不产生任何输出并进入循环是单词的预期行为(它还能做什么?)