6

我有一个使用dosyncand创建的循环图ref-set。当我将它传递给我时,println我得到了java.lang.StackOverflowError我所期望的 a,因为它实际上是在尝试打印一个无限嵌套的结构。

我发现,如果我这样做(str my-ref),它会创建一些看起来像vertex@23f7d873但实际上并没有尝试遍历结构并将所有内容打印出来的东西,所以这从直接意义上解决了问题,但只有当我非常小心我的内容时才有帮助m 打印到屏幕上。我希望能够调用(println my-graph)让它打印ref为某种类型的自定义文本(可能涉及str),以及其他非参考的东西。

目前我有一个自定义打印功能,它自己打印结构的每个元素并完全跳过打印ref. (事实证明,看vertex@23f7d873实际上并不是很有用)。这使用起来很尴尬,并且极大地阻碍了在 REPL 中对内容进行随意检查,并且还阻止了 Emacs 检查员在我处于swank.core/break调试状态时查看内容。

一个细节是ref实际上是 a 中的一个值defstruct,它还包含我试图正常打印的一些其他内容。

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

  1. 找出协议extend-type并将其应用于CharSequencemy defstructed 结构,以便在遇到 a 时ref它可以正常工作。这仍然需要对结构进行逐个字段的检查,并在涉及到 时需要特殊情况ref,但至少它将问题定位到结构而不是包含该结构的任何东西。
  2. 找出CharSequence在遇到ref. 这允许更本地化的行为,并允许我在 REPL 上查看循环引用,即使它不在结构内。这是我的首选。
  3. 弄清楚如何做一些toString我认为在我做的时候在某种程度上被调用的事情println。我对这个选项最无知。对其他的也很无知,但我一直在阅读Joy of Clojure,现在我都受到了启发。

同样,此解决方案应适用于printpprint其他任何在尝试打印循环引用时通常会出错的东西。我应该采用什么策略?

非常感谢您的任何意见。

4

3 回答 3

4

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

详细地:

    创建一个不包括 pr-str 和 print-method 的 ns。创建一个多方法打印方法(完全复制 clojure.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))

于 2012-04-04T14:29:56.827 回答
4

我找到了我的解决方案——只需创建一个为每种特定类型重载的多clojure.core/print-method方法,然后从多方法中调用泛型函数。在这种情况下,每个多方法调用print-graph-element知道如何处理引用的泛型(它只是打印出被引用对象的哈希值,而不是试图打印出被引用对象的值)。在这种情况下,我假设 print-method 的调度函数就是(type <thing>)这样调度类型的,这就是我编写多方法的方式。我实际上找不到任何文档说明 print-method dispatch 是如何工作的,但它确实看起来是这样的。

此解决方案允许我只键入对象的名称,例如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))
于 2012-04-14T10:42:58.640 回答
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 很有用。

于 2012-04-14T15:31:55.447 回答