This was a microblog post at first, but I'm crossposting it for visibility and because I'm not 100% convinced the microblog won't collapse at any moment.

VSAs are an acyclic e-graph rooted at a single e-class

I've thought that VSAs were acyclic e-graphs for a while but I never actually checked the e-graph paper. Now I'm pretty convinced.

This gh thread/short paper discuss some relationship between e-graphs and VSAs, but don't show the actual definition

Program := Value
     | App { op: Function, children: List[Program] }

-- by FlashMeta etc
VSANode := Leaf { asts: Set[Program] }
     | Join { op: Function, children: List[VSANode] }
     | Union { children: Set[VSANode] }

-- invariant: all programs represented 
        by a VSANode are equivalent
-- Leaf: all the children asts must be equivalent
-- Join: inductively all the children are equal, 
     so it's by operator property
-- Union: also inductive

-- based on the egg paper 
-- (
-- but I interpreted some stuff 
-- because there's a lot of indirection
ENode := Final Program
     | Join { op: Function, children: List[EClass] }
EClass := Set[ENode]
EGraph := Set[EClass]

So if you observe that each case of an ENode can represent a (possibly singleton) set on its own, then you can collapse it into one definition and treat the whole thing as a set. Then, the main difference is that an EGraph makes the distinction that a Union represents an EClass and lets an EGraph be a set of e-classes. This definition makes a lot of sense when cycles are allowed, since there's not a certain root.

You could also reuse the VSANode definition and let EGraph := Set[VSANode], assuming that your type system can't check that it's acyclic

The indirection i referred to in the egg paper is to do with e-class IDs and sticking stuff in an arena, but for VSAs I just used pointers. In an older version of my interactive-vsa site I actually use the Rc address as the id of the html node