Data.Stream interleave implementation question

I'm in the process of learning Haskell and am implementing my own Stream type. ( I'm working my way through homework problems I found at http://www.seas.upenn.edu/~cis194/hw/06-laziness.pdf ) One of the questions is: "Define the stream ruler :: Stream Integer which corresponds to the ruler function 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, . . . where the nth element in the stream (assuming the first element corresponds to n = 1) is the largest power of 2 which evenly divides n. " I got pretty close, but ended up looking at Data.Stream to nail down my mistake. My original solution was: ----- data Stream a = Cons a (Stream a) deriving (Eq, Ord) streamRepeat :: a -> Stream a streamRepeat a = Cons a (streamRepeat a) interleaveStreams :: Stream a -> Stream a -> Stream a interleaveStreams (Cons a as) (Cons b bs) = Cons a (Cons b (interleaveStreams as bs)) ruler :: Stream Integer ruler = foldr1 interleaveStreams (map streamRepeat [0..]) ----- Attempting to evaluate 'ruler' in ghci does not finish. I knew something was wrong with my implementation of interleaveStreams because it worked when I expanded it out manually for a few steps. The way Data.Stream implements interleave is: interleaveStreams (Cons a as) bs = Cons a (interleaveStreams bs as) I don't understand why this works and why my original implementation doesn't. I figure if the latter works, the former should as well. Here is my reasoning: In the latter definition, Cons a (interleaveStreams bs as), (interleaveStreams bs as) is a thunk. The thunk should only be evaluated when it is needed. In my original definition, (interleaveStreams as bs) is a thunk. The difference is an extra Cons (e.g., Cons b). It seems like the additional Cons is causing problems, I just don't understand why. Can anyone point out what I'm missing? thanks, bb

On Wed, Feb 12, 2014 at 11:23 AM, Bryan Brady
ruler :: Stream Integer ruler = foldr1 interleaveStreams (map streamRepeat [0..])
This is an interesting solution. I'd hazard to say that it's probably not the solution the author had in mind, so bonus points for novelty! How would you prove it correct though? -- Kim-Ee

On Wed, Feb 12, 2014 at 3:10 AM, Kim-Ee Yeoh
I'd hazard to say that it's probably not the solution the author had in mind, so bonus points for novelty!
What solution do you think the author had in mind? He did hint to use define and use interleaveStreams and to avoid checking divisibility testing.
How would you prove it correct though?
I haven't attempted this yet, but if I were to, I'd go for an inductive proof. If you list the solution along with the numbers they correspond to (divide), you get: 1 2 3 4 5 6 7 8 9 10... 0 1 0 2 0 1 0 3 0 1... If you drop all the numbers corresponding to a 0 in the ruler function: 2 4 6 8 10... 1 2 1 3 1 ... then drop the numbers corresponding to a 1 in the ruler function: 4 6 ... 2 3 ... At each level, you drop every other number, and the first number of each successive level is 1 greater than the previous level. Formalize that a bit and you got a proof.

On Wed, Feb 12, 2014 at 8:05 PM, Bryan Brady
What solution do you think the author had in mind? He did hint to use define and use interleaveStreams and to avoid checking divisibility testing.
Recall the famous haskell fibonacci: fib = 0 : 1 : some_function_of fib Mathematically speaking, what fractal property does the ruler sequence observe that you could similarly exploit? As for your solution, I don't doubt for a minute it's correct (I probably could have worded what I said better). The hope is that a proof would be interesting/delightful in that ineffable math-y way. -- Kim-Ee

On Wed, Feb 12, 2014 at 03:10:21PM +0700, Kim-Ee Yeoh wrote:
On Wed, Feb 12, 2014 at 11:23 AM, Bryan Brady
wrote: ruler :: Stream Integer ruler = foldr1 interleaveStreams (map streamRepeat [0..])
This is an interesting solution.
I'd hazard to say that it's probably not the solution the author had in mind, so bonus points for novelty!
You're right that it's not the exact solution I had in mind, but I like it! I had in mind a more directly recursive version of ruler; I think proving the two solutions equivalent should not be too hard. -Brent

On 02/11/2014 11:23 PM, Bryan Brady wrote:
[...] interleaveStreams :: Stream a -> Stream a -> Stream a interleaveStreams (Cons a as) (Cons b bs) = Cons a (Cons b (interleaveStreams as bs))
ruler :: Stream Integer ruler = foldr1 interleaveStreams (map streamRepeat [0..]) [...] In the latter definition, Cons a (interleaveStreams bs as), (interleaveStreams bs as) is a thunk. The thunk should only be evaluated when it is needed. In my original definition, (interleaveStreams as bs) is a thunk. The difference is an extra Cons (e.g., Cons b). It seems like the additional Cons is causing problems, I just don't understand why. Can anyone point out what I'm missing?
The issue is on the left-hand-side of interleaveStreams. It is strict in both its arguments. Look at foldr1; you're evaluating interleaveStreams (streamRepeat 0) (interleaveStreams (streamRepeat 1) (interleaveStreams (streamRepeat 2) (interleaveStreams (streamRepeat 3) ... In this situation, if interleaveStreams evaluates its second argument before returning any work, it will never be able to return. Happily, the outer Cons of the result does not depend on the second argument. I can fix the issue just by making the second argument be pattern-matched lazily (with ~, i.e. only as soon as any uses of the argument in the function are evaluated): interleaveStreams (Cons a as) ~(Cons b bs) = Cons a (Cons b (interleaveStreams as bs)) You can also write this without ~ by explicitly pattern matching later, e.g. interleaveStreams (Cons a as) bs = Cons a (case bs of Cons b bs' -> Cons b (interleaveStreams as bs')) I'm not sure whether there's a practical difference between these and Data.Stream's definition. Actually, I think they turn out to do exactly the same thing... (I tested by deriving Show for Stream, and typing 'ruler' in `ghci file.hs`, or 'take 80 (show ruler)'.) -Isaac

Thanks Isaac! It never crossed my mind that the problem was on the left hand side. On Wed, Feb 12, 2014 at 3:36 AM, Isaac Dupree < ml@isaac.cedarswampstudios.org> wrote:
[...] In this situation, if interleaveStreams evaluates its second argument before returning any work, it will never be able to return. Happily, the outer Cons of the result does not depend on the second argument. I can fix the issue just by making the second argument be pattern-matched lazily (with ~, i.e. only as soon as any uses of the argument in the function are evaluated):
interleaveStreams (Cons a as) ~(Cons b bs) = Cons a (Cons b
(interleaveStreams as bs))
So that's what the ~ does... :)
I'm not sure whether there's a practical difference between these and
Data.Stream's definition. Actually, I think they turn out to do exactly the same thing...
Yes, they do. I used the solution in Data.Stream to fix mine, though I didn't recognize the true source of the problem until you pointed it out. Thanks again!

On Wed, Feb 12, 2014 at 11:23 AM, Bryan Brady
In the latter definition, Cons a (interleaveStreams bs as), (interleaveStreams bs as) is a thunk. The thunk should only be evaluated when it is needed. In my original definition, (interleaveStreams as bs) is a thunk. The difference is an extra Cons (e.g., Cons b). It seems like the additional Cons is causing problems, I just don't understand why. Can anyone point out what I'm missing?
As Isaac wrote, you want to look at the _left_ hand side, not the right. It's pattern matching that impacts operational semantics. -- Kim-Ee

Funny, I was trying the same Homework, implemented interleaveStreams using the same algorithm you did (taking one item from each stream per recursive call), and got the same result with the ruler function hanging. It was also fixed by changing interleaveStreams to take from just one stream per call and switch back and forth between streams. This happened even though I implemented my ruler function itself recursively: ruler' :: Integer -> Stream Integer ruler' n = interleaveStreams (streamRepeat n) (ruler' (n + 1)) ruler = ruler' 0 Sorry, but I also don't know why. I speculate that if one were to expand it out on pencil and paper, they'd find that the interleaved stream somehow expands at a different rate than the ruler (half or twice). -Curt

I had the exact same problem. It's a consequence of "pattern matching
drives evaluation", and indeed that's the lesson I learned from the
exercise. If you match them together at the start, you end up with a
recursive call before you've generated the first item in the list. If you
just restructure it so that the second destructure happens after you've
produced the first value, everything works.
Julian.
On 18 August 2014 19:22, Curt McDowell
Funny, I was trying the same Homework, implemented interleaveStreams using the same algorithm you did (taking one item from each stream per recursive call), and got the same result with the ruler function hanging. It was also fixed by changing interleaveStreams to take from just one stream per call and switch back and forth between streams.
This happened even though I implemented my ruler function itself recursively:
ruler' :: Integer -> Stream Integer ruler' n = interleaveStreams (streamRepeat n) (ruler' (n + 1))
ruler = ruler' 0
Sorry, but I also don't know why. I speculate that if one were to expand it out on pencil and paper, they'd find that the interleaved stream somehow expands at a different rate than the ruler (half or twice).
-Curt
_______________________________________________ Beginners mailing list Beginners@haskell.org http://www.haskell.org/mailman/listinfo/beginners

On 2014-08-18 20:22, Curt McDowell wrote:
Funny, I was trying the same Homework, implemented interleaveStreams using the same algorithm you did (taking one item from each stream per recursive call), and got the same result with the ruler function hanging. It was also fixed by changing interleaveStreams to take from just one stream per call and switch back and forth between streams.
I hit the same problem when I did the exercise. In fact, it made me post this question on StackOverflow, asking about how pattern matching might cause the function to work differently: http://stackoverflow.com/questions/25078598/why-would-using-head-tail-instea... I thought the answers were quite enlightening, if only to emphasize that debugging this kind of problem is really difficult (to me). I only noticed that not using pattern matching for the second argument of 'interleave' helps by accident... -- Frerich Raabe - raabe@froglogic.com www.froglogic.com - Multi-Platform GUI Testing
participants (7)
-
Brent Yorgey
-
Bryan Brady
-
Curt McDowell
-
Frerich Raabe
-
Isaac Dupree
-
Julian Birch
-
Kim-Ee Yeoh