I toyed around with definitions to better understand the evaluation model, and wrote two for the length of a list.
The naive definition:
len :: [a] -> Int
len [] = 0
len (_:xs) = 1 + len xs
The strict (and tail-recursive) definition:
slen :: [a] -> Int -> Int
slen [] n = n
slen (_:xs) !n = slen xs (n+1)
len [1..10000000]
takes about 5-6 seconds to perform.
slen [1..10000000] 0
takes about 3-4 seconds to perform.
I'm curious why. Before I checked the performances I was positive that they would perform about the same because len
should have only one more thunk to evaluate at most. For demonstration purposes:
len [a,b,c,d]
= 1 + len [b,c,d]
= 1 + 1 + len [c,d]
= 1 + 1 + 1 + len [d]
= 1 + 1 + 1 + 1 + len []
= 1 + 1 + 1 + 1 + 0
= 4
And
slen [a,b,c,d] 0
= slen [b,c,d] 1
= slen [c,d] 2
= slen [d] 3
= slen [] 4
= 4
What makes slen
noticeably faster?
P.S. I also wrote a tail-recursive lazy function (just like slen
but lazy) as an attempt to close-in on the reason -- maybe it's because it was tail-recursive -- but it performed about the same as the naive definition.