3

My code caused some sort of infinite recursion so I decided to look through this post (OP was doing the same problem as me: implementing the ruler function on an infinite Stream):

Implementing the ruler function using `streamInterleave`

My code looked like this:

data Stream a = Cons a (Stream a)

streamRepeat :: a -> Stream a
streamRepeat x = Cons x (streamRepeat x)

interleaveStreams :: Stream a -> Stream a -> Stream a
interleaveStreams (Cons x xs) (Cons y ys) = Cons x (Cons y (streamInterleave xs ys))

ruler :: Stream Integer
ruler = interleaveStreams (streamRepeat 0) (streamMap (+1) $ ruler)

Now, it turns out that if I change the definition of interleaveStreams to

interleaveStreams :: Stream a -> Stream a -> Stream a
interleaveStreams (Cons x xs) ys = Cons x (interleaveStreams ys xs)

and leave everything else as is, there is no infinite recursion and it works properly. As far as I know, the two versions of interleaveStreams are equivalent. Why does one of them hang, while the other one works properly?

Agent 008
  • 141
  • 8
  • An aside: `streamRepeat x = let r = Cons x r in r`, or `streamRepeat x = fix (Cons x)`, is better because it produces a *cyclical* structure instead of one that grows without bound. – dfeuer Oct 04 '19 at 05:11

2 Answers2

5

Suppose you need only the head of Cons x (Cons y (streamInterleave xs ys)). The hanging implementation of interleaveStreams still says it needs to match both Cons x xs and Cons y ys to do that. This is enough to break some operations you’d probably like to be valid, when the value that matches Cons y ys is a function of x.

That’s the case for ruler = interleaveStreams (streamRepeat 0) (streamMap (+1) $ ruler)! To simplify, streamMap doesn’t change anything about this:

ruler = interleaveStreams (streamRepeat 0) ruler

and ignoring streamRepeat:

interleaveStreams (Cons y ys) = Cons 0 (Cons y (streamInterleave xs ys))
ruler = interleaveStreams ruler

you can see why Haskell can’t figure out what y is.

Another option is to use a lazy pattern:

interleaveStreams (Cons x xs) ~(Cons y ys) = Cons x (Cons y (streamInterleave xs ys))

Or you could give ruler a head start:

ruler = Cons 0 (interleaveStreams (streamMap (+1) ruler) (streamRepeat 0))
Ry-
  • 218,210
  • 55
  • 464
  • 476
2

Before the body of interleaveStreams can be evaluated, both parameters need to be destructured.

But when you call interleaveStreams from ruler, the second parameter is ruler itself.

And so it turns out that before the body of interleaveStreams can be evaluated, ruler needs to be evaluated at least partially, at least enough to be destructured.

But the whole body of ruler is a call to interleaveStreams, so ruler cannot be evaluated until interleaveStreams is evaluated. That's your infinite loop.

In the alternative definition of interleaveStreams, you produce a Cons result without needing to destructure the second parameter, so evaluation can proceed.

Fyodor Soikin
  • 78,590
  • 9
  • 125
  • 172