18

If I have a recursive ADT

data MyType = A | B | C MyType | D MyType MyType

I could write a function to determine whether an instance of MyType contains an A like so:

hasA :: MyType -> Bool
hasA A = True
hasA B = False
hasA (C x) = hasA x
hasA (D x y) = (hasA x) || (hasA y)

This would work for acyclic instances, but it does not halt for cyclic structures, e.g.

let x = C x in hasA x

Instead, in this example it should return False. In other cases (making use of D) it would erroneously not halt instead of returning True.

So, the question is how do I most easily write functions like hasA that work on cyclic structures? Racket has a particularly nice feature for this in the form of define/fix, that allows you to make a function like hasA behave as intended and return False for the structure in the example above, with hardly any extra code. Is there a way of doing something similar in Haskell? Is there an extension for it?

Edit: I have now found that define/fix is in fact a macro created by Matt Might that takes advantage of Racket's meta-programming features, not a built-in feature, but this does not make it any less of a great feature of Racket. Maybe this macro could be reproduced in Haskell?

Taylan Aydinli
  • 4,130
  • 15
  • 36
  • 32
Dylan
  • 1,542
  • 1
  • 10
  • 21
  • 2
    you'll definitely have to reify cycles in your graph, or keep track of the data you need for `hasA` in some other way when you construct it (e.g. at the top level have a `Set` of all the structures below). This might have some good info for you: http://stackoverflow.com/questions/9732084/how-do-you-represent-a-graph-in-haskell – jberryman Oct 12 '13 at 17:57
  • 1
    @jberryman What about using `StableName` for equality tests to detect cycles? Is there a downside to that that I have missed? And thank you very much for that link. – Dylan Oct 12 '13 at 18:21
  • @Dylan using `StableName` to check for equality should be fine for this. The only downside I can think of is that it operates in IO. – John L Oct 12 '13 at 19:18
  • @JohnL Would it be "safe" to use that with unsafePerformIO for this purpose? – Dylan Oct 12 '13 at 22:30
  • 2
    @Dylan: No! Purity is exactly why `StableName` lives in `IO`. Equational reasoning says that `let x = C x in hasA x` should be the same as `let x = C x in hasA (C x)`, but `StableName`s would let you violate that. – Antal Spector-Zabusky Oct 13 '13 at 00:09

1 Answers1

11

The key words to search for on Hackage are observable sharing. The data-reify package in those results looks especially relevant:

data-reify provided [sic] the ability to turn recursive structures into explicit graphs. Many (implicitly or explicitly) recursive data structure can be given this ability, via a type class instance. This gives an alternative to using Ref for observable sharing.

Daniel Wagner
  • 128,625
  • 9
  • 198
  • 347