[英]How to override println behavior for reference types
我有一个使用dosync
和ref-set
创建的循环图。 当我将它传递给println
时,我得到了一个java.lang.StackOverflowError
正如我所料,因为它实际上是在尝试打印一个无限嵌套的结构。
我发现如果我这样做(str my-ref)
它会创建一些看起来像vertex@23f7d873
的东西并且实际上不会尝试遍历结构并打印出所有内容,所以这在直接意义上解决了问题,但只有在我对要打印到屏幕上的内容非常小心。 我希望能够调用(println my-graph)
让它打印ref
作为某种类型的自定义文本(可能涉及str
),以及其他非 ref 内容。
目前我有一个自定义打印 function,它单独打印结构的每个元素并完全跳过打印ref
。 (事实证明,查看vertex@23f7d873
实际上并不是很有用)。 这使用起来很尴尬,并且极大地阻碍了在 REPL 中对内容进行随意检查,并且还阻止了 Emacs 检查员在我进行swank.core/break
调试时查看内容。
一个细节是ref
实际上是defstruct
中的一个值,它还包含一些我试图正常打印的其他内容。
所以我想知道我应该把 go 放在什么路径下。 我看到这些选项:
extend-type
并将CharSequence
协议应用于我的defstruct
ed 结构,以便在遇到ref
时它可以正常工作。 这仍然需要逐个字段检查结构和涉及ref
的特殊情况,但至少它将问题定位到结构而不是包含结构的任何内容。ref
时如何覆盖CharSequence
协议。 这允许更多的本地化行为,并允许我在 REPL 中查看循环引用,即使它不在结构内也是如此。 这是我的首选。toString
做某事,我相信当我执行println
时,它会在某种程度上被调用。 我对这个选项最无知。 对其他人也一无所知,但我一直在阅读Joy of Clojure
,现在我都受到了启发。 同样,此解决方案应适用于print
和pprint
以及在尝试打印循环引用时通常会出现的任何其他问题。 我应该采用什么策略?
非常感谢任何输入。
你想要做的是创建一个新的命名空间并定义你自己的打印函数,模拟 clojure 打印对象的方式,并默认为 clojure 的方法。
详细地:
作为奖励,如果您使用的是 clojure 1.4.0,则可以使用标记文字,以便也可以读取 output。 您需要覆盖自定义标记的*data-readers*
map 和返回 ref object 的 function。您还需要覆盖读取字符串行为以确保为*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))
我找到了我的解决方案——只需创建一个为每个特定类型重载clojure.core/print-method
的多方法,并从多方法中调用通用 function。 在这种情况下,每个 multimethod 调用知道如何处理引用的通用print-graph-element
(它只是打印出引用的 object 的 hash,而不是尝试打印出引用对象的值)。 在这种情况下,我假设 print-method 的调度 function 只是(type <thing>)
所以它按类型调度,这就是我编写 multimethod 的方式。 我实际上找不到任何文档说明打印方法调度是如何工作的,但它确实看起来是那样的。
该解决方案允许我仅键入 object 的名称,例如v0
(由 repl 中的 (make-vertex) 生成,repl 调用打印方法,从而防止我的 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))
如果您只想能够打印带有循环的数据结构,请尝试设置*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*
变量,当你处理无限长度的数据时它会很有用。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.