Would the ability to detect cyclic lists in Haskell break any properties of the language?
Asked Answered
H

2

19

In Haskell, some lists are cyclic:

ones = 1 : ones

Others are not:

nums = [1..]

And then there are things like this:

more_ones = f 1 where f x = x : f x

This denotes the same value as ones, and certainly that value is a repeating sequence. But whether it's represented in memory as a cyclic data structure is doubtful. (An implementation could do so, but this answer explains that "it's unlikely that this will happen in practice".)

Suppose we take a Haskell implementation and hack into it a built-in function isCycle :: [a] -> Bool that examines the structure of the in-memory representation of the argument. It returns True if the list is physically cyclic and False if the argument is of finite length. Otherwise, it will fail to terminate. (I imagine "hacking it in" because it's impossible to write that function in Haskell.)

Would the existence of this function break any interesting properties of the language?

Holism answered 16/5, 2015 at 6:45 Comment(4)
Well, you suddenly can return different values for the same inputs: f x = if isCycle x then 1 else 2. This would return 1 or 2 for the value ones depending on how you build that same list. This seems pretty big a breakage considering that one of the most obvious advantages of functional languages is that a function will always return the same result if given the same values in input...Syverson
This is only ever considered acceptable when constrained to IO. You can write isCycle :: [a] -> IO Bool in terms of an explicit structure of the graph of a list obtained from data-reify which internally uses StableNames. If you look too hard at memory with IO you can do absolutely evil things, like break the purity of pure functions without resorting to anything unsafe.Rude
Checking if a list is physaclly cyclic would require to check all the nodes until a cycle is found. This wont' terminate on infinite list in Haskell. However it will in Lisp (and probably all imperative languages), because cyclic list is the only way to do infinite list.Toxoid
@Syverson The function I proposed "returns True if the list is physically cyclic and False if the argument is of finite length. Otherwise it will fail to terminate." So the function f you proposed certainly would not return 2 for the value ones.Holism
T
12

Would the existence of this function break any interesting properties of the language?

Yes it would. It would break referential transparency (see also the Wikipedia article). A Haskell expression can be always replaced by its value. In other words, it depends only on the passed arguments and nothing else. If we had

isCycle :: [a] -> Bool

as you propose, expressions using it would not satisfy this property any more. They could depend on the internal memory representation of values. In consequence, other laws would be violated. For example the identity law for Functor

fmap id === id

would not hold any more: You'd be able to distinguish between ones and fmap id ones, as the latter would be acyclic. And compiler optimizations such as applying the above law would not longer preserve program properties.

However another question would be having function

isCycleIO :: [a] -> IO Bool

as IO actions are allowed to examine and change anything.

A pure solution could be to have a data type that internally distinguishes the two:

import qualified Data.Foldable as F

data SmartList a = Cyclic [a] | Acyclic [a]

instance Functor SmartList where
    fmap f (Cyclic xs) = Cyclic (map f xs)
    fmap f (Acyclic xs) = Acyclic (map f xs)

instance F.Foldable SmartList where
    foldr f z (Acyclic xs) = F.foldr f z xs
    foldr f _ (Cyclic xs) = let r = F.foldr f r xs in r

Of course it wouldn't be able to recognize if a generic list is cyclic or not, but for many operations it'd be possible to preserve the knowledge of having Cyclic values.

Troposphere answered 16/5, 2015 at 16:46 Comment(1)
I think you mean fmap id ones, not fmap ones.Parallax
T
0

In the general case, no you can't identify a cyclic list. However if the list is being generated by an unfold operation then you can. Data.List contains this:

unfoldr :: (b -> Maybe (a, b)) -> b -> [a]

The first argument is a function that takes a "state" argument of type "b" and may return an element of the list and a new state. The second argument is the initial state. "Nothing" means the list ends.

If the state ever recurs then the list will repeat from the point of the last state. So if we instead use a different unfold function that returns a list of (a, b) pairs we can inspect the state corresponding to each element. If the same state is seen twice then the list is cyclic. Of course this assumes that the state is an instance of Eq or something.

Talent answered 16/5, 2015 at 15:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.