Ok basically I have a problem knowing whether option 1 or 2 applies in the following case:
naturals = 0 : map (+ 1) naturals
Where options are:
1. The execution is awful, everything is recomputed at each step:
naturals = [0]
naturals' = 0:map (+ 1) [0] // == [0, 1]
naturals'' = 0:map (+ 1) [0, 1] // == [0, 1, 2]
naturals''' = 0:map (+ 1) [0, 1, 2] // == [0, 1, 2, 3]
naturals'''' = 0:map (+ 1) [0, 1, 2, 3] // == [0, 1, 2, 3, 4]
2. The execution is not awful, the list is always infinite and map is applied once only
naturals = 0:something
|
naturals' = 0: map (+ 1) (0: something)
|
naturals'' = 0:1: map (+ 1) (0:1: something')
|
naturals''' = 0:1:2: map (+ 1) (0:1:2: something'')
|
naturals'''' = 0:1:2:3:map (+ 1) (0:1:2:3:something''')
with | indicating where map is at in its execution.
I do know that answers may be only 1 or 2 but I’d appreciate some pointers to good explanations on co-recursion too to clear the last doubts 🙂
Execution isn’t going to be, as you put it, “awful”. 🙂 Lazy evaluation is your best friend here. What does laziness mean?
“Things”, here, are “not-yet-evaluated expressions”, also known as “thunks”.
Here is what happens:
You define
Merely defining
naturalsdoesn’t introduce a need to evaluate it, so initiallynaturalswill just point to a thunk for the unevaluated expression0 : map (+1) naturals:At some point, your program may pattern match on naturals. (Pattern matching is essentially the only thing that forces evaluation in a Haskell program.) That is, your program needs to know whether naturals is the empty list or a head element followed by a tail list. This is where the right-hand side of your definition will be evaluated, but only as far as needed to find out whether
naturalsis constructed by[]or(:):That is naturals will now point to an application of the constructor
(:)on the head element0and a thunk for the still-unevaluated tail. (Actually, the head element will also be still-unevaluated, so reallynaturalswill point to something of the form<thunk> : <thunk>, but I will be leaving that detail out.)It is not until some later point in your program where you may pattern match on the tail that the thunk for the tail gets “forced”, i.e., evaluated. This means that the expression
map (+1) naturalsis to be evaluated. Evaluating this expression reduces tomappattern matching onnaturals: it needs to know whethernaturalsis constructed by[]or(:).We saw that, at this point, rather than pointing to a thunk,
naturalsis already pointing to an application of(:), so this pattern match bymaprequires no further evaluation. The application ofmapdoes immediately see enough ofnaturalsto figure out that it needs to produce an application of(:)itself and so it does:mapproduces1 : <thunk2>where the thunk contains an unevaluated expression of the formmap (+1) <?>. (Again, instead of1, we actually have a thunk for0 + 1.) What is<?>pointing to? Well, the tail ofnaturals, which happens to be whatmapwas producing. Hence, we now havewith
<thunk2>containing the yet-unevaluated expressionmap (+1) (1 : <thunk2>).At yet a later point in your program, pattern matching may force
<thunk2>, so that we getwith
<thunk3>containing the yet-unevaluated expressionmap (+1) (2 : <thunk3>). And so forth.