5

The tying-the-knot strategy can be used to construct graphs such as, using a simple two-edged graph as an example:

data Node = Node Node Node

-- a - b
-- |   |
-- c - d
square = a where
    a = Node b c
    b = Node a d
    c = Node a d
    d = Node b c

That strategy is rather elegant, but I couldn't find a way to actually use it without Int labels. For example, how could I write a function that counts the amount of nodes on the square value?

countNodes :: Node -> Int
countNodes = ... ??? ...

main = print $ countNodes square
-- output: 4
MaiaVictor
  • 51,090
  • 44
  • 144
  • 286
  • 1
    You can not solve this problem without going outside the language. Your notion of a unique label, such as an integer, is one good refactoring of the problem into something solvable. – Thomas M. DuBuisson Oct 15 '15 at 22:53
  • Can the problem be solved on the case of labelled edges? I.e., `a = Node (b,0) (c,0)`, representing that `a` is on the first slot of both `b` and `c`? – MaiaVictor Oct 15 '15 at 22:54
  • If the labels are not globally unique, then you still get the problem that you cannot identify whether two nodes are distinct. – Ørjan Johansen Oct 15 '15 at 23:26
  • 1
    Check these questions: [1](http://stackoverflow.com/questions/8945096/any-nice-tools-for-untying-knots-in-haskell), [2](http://stackoverflow.com/questions/30272662/would-the-ability-to-detect-cyclic-lists-in-haskell-break-any-properties-of-the). – effectfully Oct 16 '15 at 02:28

3 Answers3

4

In general you must be able to compare a node for equality with previously observed nodes to determine you are infact re-visiting a portion of the graph instead of having decended into a subgraph of similar structure. This is regardless of how you syntactically express your nodes or in what language.

For example, using either the provided representations it is not possible to distinguish a graph of

a - b
|   |
c - d

from

a - b
| /
c

or

a - b - c
|       |
d - e - f

or even

a - b                 a -
|   |   or heck even  |  |
- - -                  --

Each local observation is a node with two edges to indistinguishable entities.

If you either add an identifier, such as an int, to the edges or nodes, or you cheat and steal an identifier (such as an address, but in Haskell this isn't deterministic because of GC) then you might be able to use this information to infer equality or inequality.

Thomas M. DuBuisson
  • 64,245
  • 7
  • 109
  • 166
4

You do indeed need some kind of labeling, because from inside Haskell there is no way to distinguish the nodes as written. Indeed, when a Haskell compiler sees

square = a where
    a = Node b c
    b = Node a d
    c = Node a d
    d = Node b c

it is entirely legal for it to note that a and d, as well as b and c, are defined by equal expressions, and to implement each pair as one underlying object. (This is called Common Subexpression Elimination.)

In fact, it would even be legal for it to identify all four, although I doubt compilers really do that as it would require noticing that they have the exact same semantic "denotation", being all essentially different ways of writing the infinite tree t = Node t t = Node (Node ... ...) (Node ... ...) - from the point of view of denotational semantics that's the only value of your datatype that doesn't contain bottoms.

Ørjan Johansen
  • 18,119
  • 3
  • 43
  • 53
1

You can observe sharing in IO, using e.g. data-reify:

{-# LANGUAGE TypeFamilies #-}
import Data.Reify

data Node = Node Node Node
data NodeId s = NodeId s s

instance MuRef Node where
    type DeRef Node = NodeId
    mapDeRef f (Node n1 n2) = NodeId <$> f n1 <*> f n2

The implementation of countNodes is now trivial (but note that it is in IO!)

countNodes :: Node -> IO Int
countNodes n = do
    Graph nodes root <- reifyGraph n
    return $ length nodes

Your example:

square :: Node
square = a where
    a = Node b c
    b = Node a d
    c = Node a d
    d = Node b c

*Main> print =<< countNodes square
4
Cactus
  • 27,075
  • 9
  • 69
  • 149
  • What I love about these questions is they make me finally try out things like `data-reify` that I otherwise wouldn't get around to. – Cactus Oct 26 '15 at 02:32