This isn't really possible; there is no way to detect, from within pure code, that the list repeat 1
is cyclic. Indeed, it could even not be cyclic, on sufficiently esoteric implementations of Haskell.
It is technically possible on common implementations of Haskell, however, using a technique called observable sharing,1 but it's rather complicated, and unless you're a very experienced Haskell programmer, most of the time you don't really want to solve your problem with observable sharing.
Unless you have very special requirements that would make this a good idea, I would suggest instead representing the cyclic graph that your structure represents directly; for instance, using a graph library such as the standard Data.Graph or FGL.
If you do want to try observable sharing, though, I would suggest using the data-reify package (as described in Type-Safe Observable Sharing in Haskell), which only requires a simple type-class instance and has a safe, IO
-based interface; it's essentially based on memory addresses (actually, StableName
s), as you suggested.
One of the reasons you shouldn't use observable sharing unless you really have a need to is that it breaks referential transparency; for instance, it allows you to distinguish repeat 1
and 1 : repeat 1
, and even let xs = 1 : xs in xs
and let f x = x : f x in f 1
, of these depending on compiler optimisations!
1 The basic idea behind observable sharing is to expose the sharing done by standard Haskell implementations, which can be basically summarised as "duplicating values doesn't copy them"; so let xs = 1:xs in xs
is a cyclic list, because the same value for the entirety of xs
is used for its tail, rather than recomputing it each time, and (\x -> (x,x)) expensive
(where expensive
is some long-running computation that produces a large result) just results in two pointers to expensive
, rather than copying the thunk (which means that, even if you force both fields, the computation will only be done once, and the result will only be stored once in memory).
Data.Traversable
or something, well must make sure it stays finite of course, not sure if there should be a separate typeclass for finiteized traversals or soemthing. – Enforcement