2

This is a question from one of my homework assignments that I haven't been able to answer. It's to do with reasoning about Haskell code by demonstrating how the Haskell compiler (interpreter?) executes programs.

I'm given a few different functions...

-- built-in
take :: Int -> [a] -> [a] 
take 0 _ = [] 
take n (x:xs) = x : (take (n - 1) xs) 

-- exchanging entries
exchange :: [a] -> [a] 
exchange [x,y] = [y,x] 
exchange (x:y:xs) = (y:x:(exchange xs))

-- picking even numbered entries
evens :: [a] -> [a] 
evens [x,y] = [x] 
evens (x:_:xs) = x:(evens xs) 

-- first four numbers repeated
first_four :: [Int] 
first_four = 1:2:3:4:first_four 

Now I have to demonstrate understanding of Lazy Evaluation by "pretending to be the compiler". By breaking down how this statement would be executed...

> take 5 (evens (exchange first_four))
[2,4,2,4,2] 

I'm given the first few lines to help get started...

take 5 (evens (exchange first_four)) =
take 5 (evens (exchange (1:2:3:4:first_four))) =
take 5 (evens (2:1:(exchange (3:4:first_four)))) =
...

I'd like some help with understanding how lazy evaluation works so I can do this question.

leftaroundabout
  • 117,950
  • 5
  • 174
  • 319
  • Have you tried reading any of the numerous resources already available online before coming here? – Bartek Banachewicz Jan 20 '16 at 13:35
  • @BartekBanachewicz Yes – OrbitalFriendshipCannon Jan 20 '16 at 14:01
  • I once wrote a detailed evaluation chain for a Haskell function here: http://stackoverflow.com/questions/29898999/how-does-this-list-comprehension-over-the-inits-of-itself-work/29900144#29900144 Maybe that will help you. – Sebastian Redl Jan 20 '16 at 14:10
  • 2
    I think the first evaluation step here is not very honest if you really should explain the lazy-part ... – Random Dev Jan 20 '16 at 14:13
  • @SebastianRedl That does help thank you – OrbitalFriendshipCannon Jan 20 '16 at 14:38
  • 1
    @Carsten Indeed, those lines are just plain wrong. – Sebastian Redl Jan 20 '16 at 14:48
  • @Carsten that's what I was thinking too, but actually, `evens` demands at least 3 elements from its input, and `exchange` only knows how to produce them by pairs, so demands them by pairs too from its supplier. to produce three, it must have four of them. :) this is a really trickily written code. (normally, it'd be `evens [] = [] ; ...` same for `exchange`). – Will Ness Jan 21 '16 at 16:51
  • @WillNess if you want to demonstrate laziness you have to begin by "expanding" `take` first (the rest can stay a unevaluated thunk for now) - and that is just the beginning – Random Dev Jan 21 '16 at 16:57
  • @WillNess look I don't think that us arguing will help anyone and it's a minor point but I would start the evaluation by rewriting first take and then `evens`, ... you now like `take 0 undefined`... – Random Dev Jan 21 '16 at 17:15

1 Answers1

4

Treat your definitions as re-write equations, and always (uniquely) name the interim entities as they come into play:

take 5 (evens (exchange first_four))
  -- match: take 0 .... = ....    ? FAIL
  -- match: take n (x:xs) = ....   ? SUCCESS
  n1 = 5 
  (x1:xs1) ?= evens (exchange first_four)   
    -- evens [x,y] = ....
    [x2,y2] ?= exchange first_four
      -- exchange [x,y] = ....
      [x3,y3] ?= first_four
  ......

etc. The operation is mechanical. "Laziness" here means, we proceed left-to-right, never trying to find out values of expressions too soon, only doing so when we actually need to pattern match some definition with them.

Here's what I meant by "naming" the interim entities:

take 5 (evens (exchange first_four)) =
take 5 xs   where xs = evens (exchange first_four)
       (x1:xs1) ?= xs  -- <---- THIS
                 = evens (exchange first_four)
                 = evens ys   where ys = exchange first_four
                         [x2,y2] ?= ys  -- <--- AND THIS
                         .     .  = exchange first_four
                         .     .          -- ^^ <--- already named
                         .     .       [x3,y3] ?= first_four
                         .     .               FAIL
                         .     .       (x3:y3:xs3) ?= first_four
                         .     .                    = 1:2:3:4:first_four
                         .     .               SUCCESS: x3=1
                         .     .                        y3=2
                         .     .                        xs3=3:4:first_four
                         .     .   ys = y3:x3:exchange xs3   !
                         .     .   [] ?= exchange xs3
                         .             = ...
                         .             FAIL
                         (x2:_:xs2) ?= ys
                                       SUCCESS: x2=y3
                                                xs2=...

Of course working to this fine details resolution is very tiresome and seldom needed, only perhaps to track some "timing" issues; it is easier to see what each definition does, separately, and treat them as separate producers connected in a chain.

By "timing" I mean, whether the "inner" producer is ready to produce its element when the "outer" one needs it. because if not, the whole process becomes stuck, "non-productive". Here the most inner producer is an infinite stream, directly defined, so there's no such problems.

So mentally, we could say to ourselves: "first_four is infinite repeating stream of 1 to 4; exchange swaps each pair of elements it gets; evens throws away every element in odd position; take takes n elements".

So actually, it turns out you were right all along.

Because of how the definitions are written, evens forces out of exchange its output elements in groups of four, more or less. Mainly, the culprit is the unnecessary matching for [x,y] in evens and exchange, which demands three elements from its input, to see whether the tail is empty ([]) or not.

evens demands three elements, but exchange only knows how to produce them by pairs.

But exchange too demands at least three elements from its supplier. So, coarsely:

take 5 (evens (exchange first_four))
take 5 (evens (exchange first_four))
take 5 (evens (exchange (1:2:3:(4:first_four)))
take 5 (evens (2:1:exchange (3:4:1:(2:3:4:first_four))))
take 5 (evens (2:1:4:(3:exchange (1:2:3:4:first_four))))
take 5 (2:evens (4:3:exchange (1:2:3:4:first_four)))
2:take 4 (evens (4:3:exchange (1:2:3:4:first_four)))
2:take 4 (evens (4:3:2:(1:exchange (3:4:first_four))))
2:take 4 (4:evens (2:1:exchange (3:4:first_four)))
2:4:take 3 (evens (2:1:exchange (3:4:1:(2:3:4:first_four))))
2:4:take 3 (evens (2:1:4:(3:exchange (1:2:3:4:first_four))))
2:4:take 3 (2:evens (4:3:exchange (1:2:3:4:first_four)))
........

I'm sure you can finish this now. :)

To illustrate, here's exchange looked at procedurally, as a stream transformer, which can pull an element from its input; peek whether its input has an element ready to be pulled; and push an element into its output.

exchange:
   pull(X)  ...  on_fail: ERROR
   pull(Y)  ...  on_fail: ERROR
   peek     ...  on_fail: push(Y); push(X); STOP
   push(Y); push(X)
   LOOP
Will Ness
  • 70,110
  • 9
  • 98
  • 181
  • Kinda I don't know what interim entity means though – OrbitalFriendshipCannon Jan 20 '16 at 17:28
  • the `x1` and the `xs1`, etc. What *forces* the calculation of an expression's value is the need to pattern match it, when that need arises. like with `take`, the `take 0 ... = ...` doesn't match the call `take 5 (...)`; so we move on to the next clause in `take`'s defintion, and try to match the call `take 5 (...)` with `take n (x:xs) = ...`. So `n` matches with 5, by registering `n = 5`. Next, we must see whether `(evens ...)` matches with `(x:xs)` (but, we renamed them to `x1:xs1` (NB!)). So we go on to see the definition of `evens`. And so on. -- don't hesitate to ask more. :) – Will Ness Jan 20 '16 at 20:44
  • Ok I think I get it am I on the right track with this? ... take 5 (evens (2:1:(exchange (3:4:first_four)))) = `take 5 (2:(evens (exchange 3:4:first_four)))` – OrbitalFriendshipCannon Jan 21 '16 at 14:28
  • turns out your were right all along. :) `even` demands 3 elements, but `exchange` only know how to produce them in pairs. – Will Ness Jan 21 '16 at 16:48