printing self referential data?

158 views
Skip to first unread message

Rob Nikander

unread,
Jul 23, 2017, 8:45:04 PM7/23/17
to Clojure
I'm translating some code from an object oriented language to Clojure. I'm a little confused about a tree structure I had where tree nodes have parent and children properties, so the structure forms cycles. I used atoms for those properties, so I could wire it all up. The code is clean and simple and I'm happy with it, except ... the things don't print in the REPL. (stack overflow)

Are there any tricks to printing cyclical data structures in the REPL?  

Rob

Justin Smith

unread,
Jul 23, 2017, 9:02:25 PM7/23/17
to Clojure
You can prevent the need for mutable nodes by using an adjacency list to represent a graph structure. In clojure this works nicely as a hash-map from a node id to a set of connected node ids (eg for your case, a set of parent nodes and a set of child nodes), and traversal becomes a series of lookups.

--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+u...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+u...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Didier

unread,
Jul 23, 2017, 10:12:46 PM7/23/17
to Clojure
I'm not sure I can fully help without you explaining more what you're doing. It sounds like you've got a collection or container type which has an implementation of print that loops over its elements, and calls print on them. So if you have a cycle, the print will go on forever until memory runs out.

If you're using a standard type, you might be able to limit *print-level*. Most Clojure collections will limit how deep they print based on the value of this global dynamic Var.

If you want a solution that prints the full graph starting from any node, you'll have to define a custom implementation of print-method which does a traversal of the graph and stops at cycles. The traversal you'll need to implement yourself. Print-method is only there to allow you to override the function used to print, for a specific type.

You could also look into using an existing graph library, like Loom. I believe it will print itself properly already.

Rob Nikander

unread,
Jul 24, 2017, 10:12:05 AM7/24/17
to Clojure
I think in my case here the easiest thing will be to remove the cycles, but still I'd like to understand a couple things...


On Sunday, July 23, 2017 at 10:12:46 PM UTC-4, Didier wrote:
I'm not sure I can fully help without you explaining more what you're doing. It sounds like you've got a collection or container type which has an implementation of print that loops over its elements, and calls print on them. So if you have a cycle, the print will go on forever until memory runs out.

My container type (the tree node) is simply the standard clojure map. 

If you're using a standard type, you might be able to limit *print-level*. Most Clojure collections will limit how deep they print based on the value of this global dynamic Var.

Okay, *print-level* works but it looks like I have to call print myself. I can't rely on the REPL to print it. 

    (binding [*print-level* 2] (println (find-route "/reports")))   ; okay

Why does `alter-var-root` here seem to succeed, but have no effect?

    (alter-var-root #'*print-level* (constantly 2))
    => 2
    *print-level*
    => nil
    (find-route "/reports")
    stack overflow

 

Justin Smith

unread,
Jul 24, 2017, 1:17:12 PM7/24/17
to Clojure
One important thing to be aware of that I should have mentioned when suggesting the adjacency list solution is the rationale for using that representation. When you put atoms in the nodes of your data structure, it's no longer an immutable data structure and you lose the usage patterns that clojure provides for other idiomatic data. For example if you call update or assoc you get a new data structure back that shares the same mutable atoms under the unmodified keys, which means that any modifications are reflected in all the other contexts the data is used. Of course you could (and probably should) also use defensive copying, but then you need some method of handling the cycles when copying ... in the big picture it's much simpler to use the adjacency list format which preserves the usage style we are used to with other clojure data.

--

Didier

unread,
Jul 29, 2017, 3:28:22 AM7/29/17
to Clojure
Why does `alter-var-root` here seem to succeed, but have no effect?

What you want is:

(set! *print-level* 2)

But, this will not work 100% of the time, and sometimes you might want alter-var-root.

Here's the trick. *print-level* is a dynamic Var. Alter-var-root only changes the root value of a Var, it can not change a dynamically binded value:

(def ^:dynamic x "I am root!")
x ; => "I am root!"
(binding [x "I am dynamically binded!"] x) ; => "I am dynamically binded!"
(alter-var-root #'x (constantly "I've had my root altered!"))
x ; => "I've had my root altered!"
(binding [x "I am dynamically binded!"] x) ; => "I am dynamically binded!"

So where is *print-level* binded you ask? Well, that depends on how you bootstrapped Clojure. Most of the time, such as when using lein, or clojure.main, those things will wrap your entire code in a binding, and they will bind a certain number of default dynamic var such as *print-level*. You can see the code for clojure.main here: https://github.com/clojure/clojure/blob/master/src/clj/clojure/main.clj#L76

So in your case, you're probably bootstrapping Clojure using a mechanism that binds *print-level*, thus changing its root has no effect in your code, you have to change its binding, and the function to do that is set!.
Reply all
Reply to author
Forward
0 new messages