1

This is the problem given: What are the first 8 elements in the following list?

mystery = 0 : 10 : (map(+1)mystery)

The answer is [0,10,1,11,2,12,3,13...]

But in my opinion the answer should be [0,10,1,11,1,11,2,12]. The following steps show why:

1) We are given ;list [0,10] so after applying the function the first time we have the list [0,10,1, 11] 2) Now we have a list [0,10,1,11] so after applying the function again the resulting list should be [0,10,1,11,1,11,2,12]

Apparently that is not the case. Can anyone explain why?

Will Ness
  • 70,110
  • 9
  • 98
  • 181
aquaman9
  • 35
  • 7
  • 2
    What function is it *exactly* that you claim is being applied when you write "after applying the function the first time"? – Daniel Wagner Jun 09 '19 at 07:08
  • "Why is this recursive map function only being applied to the last two elements in the list?". It's being applied to every element in the list, but only once - your version seems to apply it to 0 and 10 twice and I can't figure out why you would think it should. Also the list is infinite so it doesn't have a "last 2 elements" – Robin Zigmond Jun 09 '19 at 07:17
  • You never have a list `[0, 10]`; you have a list that *begins* with `0` and `10`, followed by... something. Conceptually, you have to distinguish between appending to a finite list "forever", and applying some function to each element of an initially infinite list. – chepner Jun 09 '19 at 14:14

3 Answers3

6

Before diving into the definition of mystery, let's take a look at one of the laws map obeys:

map f (map g xs) == map (f . g) xs

A rather informal proof of this law is easy to follow:

map f (map g [x1, x2, ..., xn]) == map f [g x1, g x2, ..., g xn]
                                == [f (g x1), f (g x2), ..., f (g xn)]
                                == [(f.g) x1, (f.g) x2, ..., (f.g) xn]
                                == map (f.g) [x1, x2, ..., xn]

With that in mind, let's expand mystery step by step:

mystery == 0 : 10 : map (+1) mystery
        -- by definition of mystery
        == 0 : 10 : map (+1) (0 : 10 : map (+1) mystery)
        -- by definition of map and the fact that  0 + 1 == 1
        == 0 : 10 : 1 : map (+1) (10 : map (+1) mystery)
        -- by definition of map and the fact that 10 + 1 == 11
        == 0 : 10 : 1 : 11 : map (+1) (map (+1) mystery)
        -- using the law above, and the fact that (+1) . (+1) == (+2)
        == 0 : 10 : 1 : 11 : map (+2) mystery
        == 0 : 10 : 1 : 11 : map (+2) (0 : 10 : map (+1) mystery)
        == 0 : 10 : 1 : 11 : 2 : map (+2) (10 : map (+1) mystery)
        == 0 : 10 : 1 : 11 : 2 : 12 : map (+2) (map (+1) mystery)
        == 0 : 10 : 1 : 11 : 2 : 12 : map (+3) mystery
        -- etc

You don't start with a finite list [0, 10]; you start with an infinite list that begins with 0 and 10, with the remaining elements defined recursively.

In some sense, there is no closed form for the list, but that doesn't matter; laziness means that you only apply map to mystery as needed to get requested items. For example, neither head mystery nor head (tail mystery) ever need to evaluate the call to map, and head (tail (tail mystery)) only needs to map (+1) to the head mystery, not the entire infinite list.

Laziness blurs the distinction between the list and the algorithm to compute the list.

ThreeFx
  • 7,250
  • 1
  • 27
  • 51
chepner
  • 497,756
  • 71
  • 530
  • 681
  • Great explanation. It should be mentioned that this explains why `mystery` has the value it has, but it does *not* explain how `mystery` is actually evaluated. In particular, `map (+1) mystery` is only calculated *once*, not once for every two elements. – dfeuer Jun 11 '19 at 15:53
4

Let's just work through it, using the recursive definition of map:

map _ [] = []
map f (x:xs) = f x : map f xs

Since we have

mystery = 0:10:(map (+1) mystery)

we already know that

mystery = [0, 10, ...]

and the ... stands for map (+1) mystery. So let's use the definition above to calculate it.

The list we're applying it to clearly isn't empty - it starts with 0 and 10. So we use the second line, with x as 0 and xs as 10:(map (+1) mystery):

map (+1) mystery = 1:(map (+1) (10:(map (+1) mystery)))

or, using the formula again for the first level of nesting:

map (+1) mystery = 1:11:(map (+1) (map (+1) mystery))

So, going back to mystery itself, we now know its first 4 elements:

mystery = [0, 10, 1, 11, ...]

and the ... stands for the contents of map (+1) (map (+1) mystery). That is, based on the result above:

map (+1) (1:11:(map (+1) (map (+1) mystery)))

I'll spare you the details of the evaluation here, because it should be clear by now what happens: the first 2 elements (which will be the 5th and 6th in mystery) will be 2 and 12, and the rest will be map (+1) ((map (+1) (map (+1) mystery))). Which, by exactly the same process, will start with 3 and 13. And so it goes on, as far as you could ever care to calculate it.

Robin Zigmond
  • 17,805
  • 2
  • 23
  • 34
1

Since

mystery = 0 : 10 : map (+1) mystery

by the definitions of (!!) and (:) and map it is the case that

mystery !! 0 = 0          -- pseudocode
mystery !! 1 = 10
mystery !! n | n > 1
             = (0 : 10 : map (+1) mystery) !! n
             = (10 : map (+1) mystery) !! (n-1)
             = (map (1+) mystery) !! (n-2)
             = 1 + (mystery !! (n-2))

and there's your answer.

An illustration:

--         0   1  2   3  4   5  6        -- n
mystery = [0, 10, 1, 11, 2, 12, 3, ...
--               /   /  /   /  /         -- (+1)
--              [0, 10, 1, 11, 2, ...
--               0   1  2   3  4         -- n-2

so all this is, is each element being defined with the reference to a previous one, two positions prior.

Thus another way to write the same thing down, making the pairing up (nay zipping) explicit, is

mystery = 0 : 10 : zipWith (+) (repeat 1)
                               mystery
-- and in general,
-- map (f y) xs  ==  zipWith f (repeat y) xs 

Translated into an imperative pseudocode, the program

main = print mystery

is actually the same as

main :
    a = 0
    b = 10
    print "[" 
    while( True ) :
       print a , 
       print b ,
       a += 1
       b += 1

A principled approach to tackle such problems is to name all your interim entities, the inner thunks behind lazy data constructors (here, :), as they come into being, forced into WHNF by the demands of lazy evaluation. Then the mystery disappears:

mystery = 0  : 10 : map (+1) mystery
        = x1 : t1 
  where 
  x1 = 0
  t1 = 10 : map (+1) mystery
     = x2 : t2
    where
    x2 = 10
    t2 = map (+1) mystery
       = map (+1) (x1 : t1)
       = x1 + 1 : map (1+) t1         -- by definition of map
       = x3     : t3
      where
      x3 = x1 + 1 = 0 + 1 = 1
      t3 = map (1+) t1 = map (1+) (x2 : t2)
         = x2 + 1 : map (1+) t2       --    = x_{n} + 1 : ...
         = x4     : t4                --    = x_{n+2}   : ...
        where
        ....

At no point do we get any function other than (1+) in this reduction process, nor do we ever get more than one of them stacked up in a row.

All we get is xn := 1 + xn-2, repeatedly, for all n above 1.

Will Ness
  • 70,110
  • 9
  • 98
  • 181