Reputation: 514
I have an infinite list, dog
(not the actual name) whose elements are generated by a somewhat-slow function, so I'm trying to avoid having to generate the same element more than once. The problem is I want the following new list, cat
:
let cat = zipWith (++) dog $ tail dog
Am I correct in believing that cat
is created by evaluating each element of dog
twice (from dog
and tail dog
) then concatenating the two elements? If so, is there a way that I can get Haskell to "realize" that the elements of dog
are precisely the same as tail dog
just shifted one to the left so that I can "pass the value" of the previous element of tail dog
to the current element of dog
? That is to say, since I know that the i-th element of dog
is equal to the (i - 1) element of tail dog
, I want my program to just re-use the (i - 1) element of tail dog
instead of recalculating it.
I know lists like the canonical creation of the Fibonacci sequence, let fib = 0:1:zipWith (+) fib $ tail fib
only evaluate the elements once; but that is because the list is defined on itself while cat
is not.
I apologize if this is a dumb question, but my brain hasn't been firing on all cylinders lately. If knowing the specific list in question will be useful, then I'll be more than happy to provide it. Thanks.
Upvotes: 1
Views: 329
Reputation: 12060
@sepp2k is correct, but at the same time, sometimes it is useful to be able to verify these things directly.... (here is was obvious, but in slightly more complicated cases, it isn't as clear).
You can always watch the program in action by using the (unsafe) trace function, like this....
import Debug.Trace
main :: IO ()
main = do
let dog = listFrom 0
cat = zipWith (++) dog $ tail dog
print $ take 10 cat
listFrom::Int->[String]
listFrom x = trace ("in listFrom: " ++ show x) $
show x:listFrom (x+1)
This will show you how many times each element was calculated (followed by the output of the program)....
in listFrom: 0
in listFrom: 1
in listFrom: 2
in listFrom: 3
in listFrom: 4
in listFrom: 5
in listFrom: 6
in listFrom: 7
in listFrom: 8
in listFrom: 9
in listFrom: 10
["01","12","23","34","45","56","67","78","89","910"]
As expected, it creates each item only once.... More interestingly, you can see that (because of laziness), no item in the list is created if you don't use the created list.... For instance, change
print $ take 10 cat
to
putStrLn "Not using cat"
and nothing gets printed out
> runProgram
Not using cat
(Just remember, though, trace
is unsafe and should never be used in the final program, it is only intended for debugging)
Upvotes: 1
Reputation: 370112
Am I correct in believing that
cat
is created by evaluating each element ofdog
twice
No, each element of a list (or more generally: each variable and each element of a algebraic data type or record) will be evaluated at most once.
I know lists like the canonical creation of the Fibonacci sequence [...] only evaluate the elements once; but that is because the list is defined on itself while cat is not.
No, that's not why. It's because a lazy list is not the same thing as the concept of a generator you might know from other languages. A lazy list is an actual data structure that exists (partly) in memory once it's been (partly) evaluated.
That is, after you've used, say, the first ten elements of the list, those elements will actually exist in memory and any subsequent usage of those elements will simply read them from memory rather than calculating them again.
Upvotes: 3