我有一个使用dosync
和ref-set
创建的循环图。当我将其传递给println
时,我会得到java.lang.StackOverflowError
,因为它有效地尝试打印无限嵌套结构。
我发现如果我做(str my-ref)
它会创建一些看似vertex@23f7d873
的东西,并且实际上并没有尝试遍历结构并将所有内容都打印出来,所以这就解决了这个问题。但只有当我非常小心我正在打印到屏幕上时才会有所帮助。我希望能够调用(println my-graph)
让它打印ref
作为某种类型的自定义文本(可能涉及str
),以及通常的其他非参考内容。
目前我有一个自定义打印功能,可以自行打印结构的每个元素,并完全跳过打印ref
。 (事实证明,vertex@23f7d873
看起来并不是非常有用。这很难使用,并且阻碍了在REPL中对内容进行随意检查,并且当我在swank.core/break
调试时,也阻止了Emacs检查员查看内容。
一个细节是ref
实际上是defstruct
中的一个值,它还包含我正在尝试正常打印的其他一些内容。
所以我想知道我应该走哪条路。我看到了这些选项:
extend-type
并将CharSequence
协议应用于我的defstruct
ed结构,这样当它遇到ref
时它就能正常运行。这仍然需要对结构进行逐个字段检查,并且在ref
时需要特殊情况,但至少它将问题本地化为结构而不是包含结构的任何内容。CharSequence
时覆盖ref
协议。这允许更加本地化的行为,并允许我在REPL处查看循环引用,即使它不在结构体内。这是我的首选。toString
执行某些操作,我相信在println
时我会在某种程度上调用它。我对这个选项最无知。对其他人一无所知,但我一直在阅读Joy of Clojure
,我现在都受到鼓舞。同样,此解决方案应适用于print
和pprint
以及在尝试打印循环引用时通常会禁止的任何其他内容。我应该采用什么策略?
非常感谢任何意见。
答案 0 :(得分:4)
你想要做的是创建一个新的命名空间并定义你自己的打印函数,这些函数模拟clojure打印对象的方式,并默认使用clojure的方法。
详细说明:
作为奖励,如果您使用的是clojure 1.4.0,则可以使用标记文字,以便也可以读取输出。您需要覆盖自定义标记的*data-readers*
映射和返回ref对象的函数。您还需要覆盖读取字符串行为以确保为*data-readers*
调用绑定。
我提供了java.io.File
的示例 (ns my.print
(:refer-clojure :exclude [pr-str read-string print-method]))
(defmulti print-method (fn [x writer]
(class x)))
(defmethod print-method :default [o ^java.io.Writer w]
(clojure.core/print-method o w))
(defmethod print-method java.io.File [o ^java.io.Writer w]
(.write w "#myprint/file \"")
(.write w (str o))
(.write w "\""))
(defn pr-str [obj]
(let [s (java.io.StringWriter.)]
(print-method obj s)
(str s)))
(defonce reader-map
(ref {'myprint/file (fn [arg]
(java.io.File. arg))}))
(defmacro defdata-reader [sym args & body]
`(dosync
(alter reader-map assoc '~sym (fn ~args ~@body))))
(defn read-string [s]
(binding [*data-readers* @reader-map]
(clojure.core/read-string s)))
现在您可以这样打电话(println (my.print/pr-str circular-data-structure))
答案 1 :(得分:4)
我找到了我的解决方案 - 只需为每个特定类型创建一个重载clojure.core/print-method
的多方法,并从多方法中调用泛型函数。在这种情况下,每个multimethod调用通用print-graph-element
,它知道如何处理引用(它只是打印出引用对象的哈希值,而不是试图打印出引用对象的值)。在这种情况下,我假设print-method的dispatch函数只是(type <thing>)
所以它会调度类型,这就是我编写多方法的方式。我实际上找不到任何文档,这是打印方法调度的工作方式,但它确实表现得那样。
这个解决方案允许我只输入对象的名称,例如v0
(由repl中的(make-vertex)调用,并且repl调用print-method,从而阻止我的StackOverflow错误。
(defmethod clojure.core/print-method vertex [v writer]
(print-graph-element v))
(defmethod clojure.core/print-method edge [e writer]
(print-graph-element e))
答案 2 :(得分:1)
如果您只想打印带循环的数据结构,请尝试设置*print-level*
以限制打印机下降的深度。
user=> (def a (atom nil))
#'user/a
user=> (set! *print-level* 3)
3
user=> (reset! a a)
#<Atom@f9104a: #<Atom@f9104a: #<Atom@f9104a: #>>>
还有一个*print-length*
var,在处理无限长度的数据时非常有用。