0

1 The Context

Consider the following snippet of Haskell:

data T = T { f1 :: String 
           , f2 :: T
           } deriving (Eq, Show)

r1 = T { f1 = "val1"
       , f2 = r2 
       } :: T

r2 = T { f1 = "val2"
       , f2 = r1
       } :: T

Notice how r1 is mutually recursive with respect to r2. AFAIK, this is totally fine in Haskell (as long as you don't use these two values in such a way that results in a mutually recursive calling loop that never terminates). For example, a user of r2 might want to access the value f1 r1 via f1 (f2 r2)(which in this case "val1"). (In fact, this is how I'm trying to use these sorts of data structures in my own code.)

2 The Problem

Now consider the following HSpec unit test:

describe "A mutually recursive value should be equal to itself" $ do
    r1 `shouldBe` r1

This causes the test compiler to stall for a while and then eventually fail. Does this mean that there is something wrong with my code, or is this just a quirk of the way shouldBe is defined (I'm assuming the way it's checking for equality isn't lazy, and involves a mutually recursive, non-terminating call)? If this is just a quirk of HSpec's shouldBe function, then is there a way to check for equality between mutually recursive values? What I ultimately want to be able to do is something like the following:

r3 = T { f1 = "val1"
       , f2 = r2 
       } :: T

describe "A mutually recursive value should be equal to itself" $ do
    r1 `shouldBe` r3
George
  • 6,927
  • 4
  • 34
  • 67
  • the problem is your `Eq` instance (laziness does not matter - `shouldBe` is of course forcing them to the `Bool` result) (and `Show` too for that matter) those will loop forever - so you should implement those yourself - either with some form of *loop*-detection or without using the recursive values at all – Random Dev May 17 '16 at 04:43

1 Answers1

1

You can't compare infinite values in finite time.

Indeed, comparing infinite values is undecidable in general, so it's impossible for the compiler to generate an Eq instance which works on those.

To stress the point, consider this code:

complexCondition :: Integer -> Bool
complexCondition = ...

foo :: Integer -> T
foo n | complexCondition n = T { f1 = "found!", f2 = foo (n+1) }
      | otherwise          = T { f1 = "not yet", f2 = foo (n+1) }

reference :: T
reference = T { f1 = "not yet", f2 = reference }

We would have foo 0 == reference is and only if complexCondition n is false for all values n. This is unfeasible to check.

However, since the comparison problem on T is coRE (its complement is recursively enumerable), then we can semi-decide the complement. Indeed, the generated Eq instance will return False in finite time for different Ts. This is feasible since only a finite prefix of such values need to be inspected to be able to state False.

chi
  • 111,837
  • 3
  • 133
  • 218