繁体   English   中英

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

[英]How to override println behavior for reference types

我有一个使用dosyncref-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 放在什么路径下。 我看到这些选项:

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

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

非常感谢任何输入。

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

详细地:

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

作为奖励,如果您使用的是 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM