为什么这些功能会有不同的评估

时间:2014-12-19 15:12:42

标签: haskell lazy-evaluation

我正在尝试使用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

2 个答案:

答案 0 :(得分:8)

您的第一个版本只需要在acchead上进行评估tail,因此在c == d时不会进行评估。

第二个版本需要先知道acc是否为空,然后才能执行任何其他操作,因为如果模式不匹配,其他代码都不能执行。这意味着即使acc也必须评估c == d。这会导致无限循环。

您可以使用这样的无可辩驳的模式使第二个版本工作:

f' d c ~(currentWord:wordsSoFar) =

通过使模式无可辩驳,您说您知道模式将匹配,因此不需要检查。如果acc为空,则会导致错误发生时(以及是否)使用currentWordwordsSoFar而不是立即发生的非详尽模式错误(无论是否实际使用了currentWordwordsSoFar

答案 1 :(得分:2)

我认为这个应该是等价的:

f d c acc | c == d = [] : acc
f d c (currentWord:wordsSoFar) = (c : currentWord) : wordsSoFar

请注意,如果c == d,我们不会尝试确定acc是否为空。 (如果c == dacc == [],您的代码的所有版本都将失败;我认为这绝不会发生。)