如何覆盖引用类型的println行为

时间:2012-04-04 04:49:41

标签: graph clojure cyclic charsequence pprint

我有一个使用dosyncref-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中的一个值,它还包含我正在尝试正常打印的其他一些内容。

所以我想知道我应该走哪条路。我看到了这些选项:

  1. 找出extend-type并将CharSequence协议应用于我的defstruct ed结构,这样当它遇到ref时它就能正常运行。这仍然需要对结构进行逐个字段检查,并且在ref时需要特殊情况,但至少它将问题本地化为结构而不是包含结构的任何内容。
  2. 了解如何在遇到CharSequence时覆盖ref协议。这允许更加本地化的行为,并允许我在REPL处查看循环引用,即使它不在结构体内。这是我的首选。
  3. 弄清楚如何使用toString执行某些操作,我相信在println时我会在某种程度上调用它。我对这个选项最无知。对其他人一无所知,但我一直在阅读Joy of Clojure,我现在都受到鼓舞。
  4. 同样,此解决方案应适用于printpprint以及在尝试打印循环引用时通常会禁止的任何其他内容。我应该采用什么策略?

    非常感谢任何意见。

3 个答案:

答案 0 :(得分:4)

你想要做的是创建一个新的命名空间并定义你自己的打印函数,这些函数模拟clojure打印对象的方式,并默认使用clojure的方法。

详细说明:

    创建一个ns,不包括pr-str和print-method。 创建一个多方法打印方法(完全复制cl​​ojure.core中的内容) 创建一个简单委托给clojure.core / print-method的默认方法 为clojure.lang.Ref创建一个方法,它不会递归地打印所有内容

作为奖励,如果您使用的是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,在处理无限长度的数据时非常有用。