我正在尝试使用haskell,在尝试提高代码的可读性时,我突然改变了它的行为。我原以为这两种变体是等价的。
原件:
f :: Eq c => c -> c -> [[c]] -> [[c]]
f d c acc
| c == d = [] : acc
| otherwise = ([c] ++ (head acc)) : tail acc
split :: Eq a => a -> [a] -> [[a]]
split delim = foldr (f delim) [[]]
这是第二个:
f' :: Eq c => c -> c -> [[c]] -> [[c]]
f' d c (currentWord:wordsSoFar)
| c == d = [] : currentWord : wordsSoFar
| otherwise = (c : currentWord) : wordsSoFar
split' :: Eq a => a -> [a] -> [[a]]
split' delim = foldr (f' delim) [[]]
以下是运行两者的结果:
*Main> take 1 (split 5 [1..])
[[1,2,3,4]]
*Main> take 1 (split' 5 [1..])
*** Exception: stack overflow
答案 0 :(得分:8)
您的第一个版本只需要在acc
和head
上进行评估tail
,因此在c == d
时不会进行评估。
第二个版本需要先知道acc是否为空,然后才能执行任何其他操作,因为如果模式不匹配,其他代码都不能执行。这意味着即使acc
也必须评估c == d
。这会导致无限循环。
您可以使用这样的无可辩驳的模式使第二个版本工作:
f' d c ~(currentWord:wordsSoFar) =
通过使模式无可辩驳,您说您知道模式将匹配,因此不需要检查。如果acc
为空,则会导致错误发生时(以及是否)使用currentWord
和wordsSoFar
而不是立即发生的非详尽模式错误(无论是否实际使用了currentWord
和wordsSoFar
。
答案 1 :(得分:2)
我认为这个应该是等价的:
f d c acc | c == d = [] : acc
f d c (currentWord:wordsSoFar) = (c : currentWord) : wordsSoFar
请注意,如果c == d
,我们不会尝试确定acc
是否为空。 (如果c == d
和acc == []
,您的代码的所有版本都将失败;我认为这绝不会发生。)