This is a variation on "Compute an (infinite) tree from fixpoint operator using delay modality".
The setting. We study a language of binary trees, augmented with the ability to refer to arbitrary other nodes in the tree by a path from the root:
type Path = [Bool]
data STree = SNode STree STree
| SPath Path
| SLeaf Int
deriving (Show)
A path is defined in the context of some root, and identifies the subtree found by successively following left/right children when we see False/True in the path. For example, the path [False, True]
identifies SLeaf 2
in the tree SNode (SNode (SLeaf 1) (SLeaf 2)) (SLeaf 3)
.
Such trees can be used, for example, to identify arbitrary flow graphs (including irreducible graphs, something that is not possible using fixpoint operators.)
We can consider the infinite tree induced by this description.
data Tree = Node Tree Tree
| Leaf Int
deriving (Show)
Here is a conversion function from one to the other, utilizing an auxiliary function follow
that finds the subtree at some path of a tree:
follow :: Path -> Tree -> Tree
follow [] s = s
follow (False : p) (Node s _) = follow p s
follow (True : p) (Node _ s) = follow p s
follow _ (Leaf _) = error "bad path"
flatten' :: Tree -> STree -> Tree
flatten' root (SPath p) = follow p root
flatten' root (SNode s1 s2) =
Node (flatten' root s1) (flatten' root s2)
flatten' _ (SLeaf i) = Leaf i
flatten :: STree -> Tree
flatten s = fix (\root -> flatten' root s)
Unfortunately, this function is not productive: it infinite loops on flatten (SPath [])
.
The problem. We now consider a variant of this structure augmented with the delay modality (as described in "Compute an (infinite) tree from fixpoint operator using delay modality"), as well as a Loop
constructor to indicate self-referential loops.
data Tree = Node (D Tree) (D Tree)
| Leaf Int
| Loop
deriving (Show)
Without using non-structurally recursive function calls (so, you can structurally recurse on STree
and Path
), write a function STree -> Tree
(or similar) which unfolds the infinite tree.
Postscript. In many cases, we don't want to compute an infinite unfolding: we want the finite unfolding if it exists, and an error otherwise. Specifically, given the original data type:
data Tree = Node Tree Tree
| Leaf Int
deriving (Show)
Without using non-structurally recursive, we might want to write a function STree -> Maybe Tree
(or similar) which unfolds the syntax into a finite tree if it exists, and fails otherwise. The lack of a delay modality in this structure guarantees that it is finite.
As there are no instances of the delay modality in this structure, it seems impossible to do this with fixD
: we will get a delayed result which we can never use. It would seem that we must convert the tree into a graph, topologically sort it, and then directly implement the algorithm ala Gaussian elimination without using fixD
.
Is there an alternate typing discipline which allows us to implement the unfoldings as elegantly as in the original (wrong) code? If so, you may just have (re)discovered another algorithm for finding cycles in graphs.