The answer related to the transfer and return of the state or the use of the state monad is more transparent than this approach, but, as mentioned in the article below, it is not so effective and does not generalize well. However, regardless of your needs for this answer, it's worth learning about state monads and working with immutable data in Haskell.
A document linked in another document gives a rather academic discussion of the use of so-called inductive graphs. Fortunately, the author of the article was kind enough to implement this approach as the Haskell library, fgl . I am going to hush up some details about attaching data to nodes and a lot, and also show how to implement DFS using this library. It is easy to modify this algorithm to create trees instead of lists, and the list version is much shorter.
dfs :: Graph gr => [Node] -> gr ab -> [Node] dfs [] _ = [] -- this equation isn't strictly necessary, but it can improve performance for very dense graphs. dfs _ g | isEmpty g = [] dfs (v:vs) g = case match vg of (Just ctx, g') -> v:dfs (suc' ctx ++ vs) g' _ -> dfs vs g
The key here is match , which breaks the graph into the so-called Context vertices, and the remaining graph (a match returns a Maybe Context to cover the case of a vertex that is not on the graph).
The concept of the vertex Context is central to the idea of โโinductive graphs: it is defined as a set
(adjIn, nodeId, nodeLabel, adjOut)
where adjIn and adjOut are lists of pairs (edgeLabel, nodeId) .
Please note that the term label is used here freely and refers to general data attached to vertices or edges.
The suc' function takes a context and returns a list of nodes that are the successors of the node in the context ( adjOut when label labels are omitted).
We can build such a graph

with code like this
testGraph :: DynGraph g => gr ab testGraph = let nodes = [(i, "N" ++ show i) | i <- [1..5]] edges = [(2,1,"E21") ,(4,1, "E41") ,(1,3, "E13") ,(3,4, "E34") ,(3,5,"E35") ,(5,2, "E52")] withNodes = insNodes nodes empty in insEdges edges withNodes
The dfs testGraph call dfs testGraph calls [1,3,4,5,2] .
Note: I was bored and stumbled upon this question, so the answer is just a record of several hours of research and experimentation.