0

This post is about fgl, the usual Haskell graph library.

Suppose I have a graph and I want to label the leaves, a leaf being a vertex without outgoing edges. I draft the trivial predicate on a context:

isLeaf :: Context a b -> Bool
isLeaf (_, _, _, [ ]) = True
isLeaf (_, _, _, _  ) = False

— And then I draft an equally trivial, if verbose, function to label a context with a predicate.

mark :: (Context a b -> Bool) -> Context a b -> Context (a, Bool) b
mark p x@(edgesIn, identifier, label       , edgesOut)
       = (edgesIn, identifier, (label, p x), edgesOut)

What could go wrong. And yet.

λ ab
mkGraph [(1,'a'),(2,'b')] [(1,2,()),(2,1,())]
λ gmap (mark isLeaf) ab
mkGraph [(1,('a',False)),(2,('b',True))] [(1,2,()),(2,1,())]

ab, being a cycle, has no leaves, and yet a node is marked. After a bit of thinking, I understand that this is due exactly to the inductive nature of the function gmap: when it removes a context from ab, it modifies the context of the remaining nodes, removing the edges they share with the departed context.

But this is not what I want. It gives me a stack; what I want is a zipper. I want to have my symmetry. I am sure I can hack around this problem, but I wonder if there is a clean, obvious, standard solution.

Ignat Insarov
  • 4,660
  • 18
  • 37

1 Answers1

1

If you want to look at the context in the original graph, then you need a copy of the original graph:

isLeaf :: Graph gr => gr a b -> Context a b -> Bool
isLeaf g (_, n, _, _) = null (suc g n)

Then:

> gmap (mark (isLeaf ab)) ab
mkGraph [(1,('a',False)),(2,('b',False))] [(1,2,()),(2,1,())]
Daniel Wagner
  • 145,880
  • 9
  • 220
  • 380
  • @IgnatInsarov You can make it inductive by recursively matching, and keeping track of the boundary between the matched and unmatched parts of the graph (as you go, insert predecessors of nodes you process into the set and delete the nodes you process from the set). But I don't think the asymptotics will be better, and it will be much harder to read. I also considered cooking up a more modern generalization of `gmap` that lets you operate in some `Applicative` (so that you could store the boundary set in `State`, say, instead of writing a custom recursion), but it has similar drawbacks. – Daniel Wagner Oct 30 '19 at 17:26
  • However, the problem would not exist if the _"suspended"_ edges were not removed from the graph in the first place. It is inductive on the nodes, and it still would be. – Ignat Insarov Oct 30 '19 at 19:34
  • @IgnatInsarov No, that would not be inductive, for essentially analogous reasons that inductive processing of trees cannot see the parents of a node, only its descendants. – Daniel Wagner Oct 30 '19 at 19:44
  • I am not sure I follow. When you are saying _"inductive processing of trees cannot see the parents of a node"_, you are meaning other _nodes_, while in this case I ask to merely suspend _edges_, and even only those attached to the boundary. As I see it, the induction on nodes is not affected by edges in any way. – Ignat Insarov Oct 30 '19 at 20:33
  • **P.S.** I actually hacked `fgl` and it works, on my few simple examples. – Ignat Insarov Oct 30 '19 at 20:45
  • It does insert some edges twice. Not sure if that can be fixed. – Ignat Insarov Oct 30 '19 at 20:59
  • @IgnatInsarov The analog of edges in standard ADTs is just other fields than the recursive one. When processing a child node, you generally do not have access to the parents' other fields; and if you want that ability, you write a custom recursion that passes it along as state. And hey presto, we're in the very situation we are with fgl: passing around some extra state about the supergraph we're a part of! – Daniel Wagner Oct 30 '19 at 23:40