I was experimenting with haskell, and while trying to improve the readability of my code I suddenly changed the behaviour of it. I would have thought these two variants would be equivalent.
Original:
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) [[]]
Here is the second one:
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) [[]]
Here are the results of running the two:
*Main> take 1 (split 5 [1..])
[[1,2,3,4]]
*Main> take 1 (split' 5 [1..])
*** Exception: stack overflow
Your first version only needs to evaluate acc when you call head and tail on it, so no evaluation takes place when c == d.
The second version needs to know whether acc is empty or not before it does anything else as none of the other code must execute if the pattern does not match. This means that acc has to be evaluated even if c == d. This leads to an infinite loop.
You can make the second version work by using an irrefutable pattern like this:
f' d c ~(currentWord:wordsSoFar) =
By making the pattern irrefutable, you're saying that you know that the pattern will match, so no check is necessary. If acc were empty, this would cause an error to happen when (and if) you used currentWord and wordsSoFar instead of a non-exhaustive pattern error happening right away (and regardless of whether currentWord and wordsSoFar are actually used).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With