The difference is entirely in the memory representation. From the point of view of the semantics of the language, they're indistinguishable—you can't write a function that can tell them apart, so your two versions of cycle
are considered two implementations of the same function (they're the exact same mapping of arguments to results). In fact, I don't know if the language definition guarantees that one of those is cyclical and the other infinite.
But anyway, let's bring out the ASCII art. Cyclical list:
+----+----+ +----+----+
| x0 | -----> ... --->| xn | |
+----+----+ +----+-|--+
^ |
| |
+--------------------------------+
Infinite list:
+----+----+
| x0 | -----> thunk that produces infinite list
+----+----+
The thing with the cyclical list is that from every cons cell in the list there is a path to all of the others and itself. This means that from the point of view of the garbage collector, if one of the cons cells is reachable, then all are. In the plain infinite list, on the other hand, there aren't any cycles, so from a given cons cell only its successors are reachable.
Note that the infinite list representation is more powerful than the cyclical one, because the cyclical representation only works with lists that repeat after some number of elements. For example, the list of all prime numbers can be represented as an infinite list, but not as a cyclical one.
Note also that this distinction can be generalized into two distinct ways of implementing the fix
function:
fix, fix' :: (a -> a) -> a
fix f = let result = f result in result
fix' f = f (fix' f)
-- Circular version of cycle:
cycle xs = fix (xs++)
-- Infinite list version of cycle:
cycle' xs = fix' (xs++)
The GHC libraries go for my fix
definition. The way GHC compiles code means that the thunk created for result
is used both as the result and the argument of the application of f
. I.e., the thunk, when forced, will call the object code for f
with the thunk itself as its argument, and replace the thunk's contents with the result.
cycle
generally gives very good results for short cycles, but for long ones, the way you use the thing will determine whether an infinite list representation will do the trick or whether you're better off changing the structure of your program. – Preferential