简体   繁体   English

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

[英]How to override println behavior for reference types

I have a cyclic graph I created using dosync and ref-set .我有一个使用dosyncref-set创建的循环图。 When I pass this to println I get a java.lang.StackOverflowError as I would expect, because it's effectively trying to print an infinitely-nested structure.当我将它传递给println时,我得到了一个java.lang.StackOverflowError正如我所料,因为它实际上是在尝试打印一个无限嵌套的结构。

I found that if I do (str my-ref) it creates something that looks like vertex@23f7d873 and doesn't actually try to traverse the structure and print everything out, so this solves the problem in the immediate sense, but only helps when I'm very careful about what I'm printing to the screen.我发现如果我这样做(str my-ref)它会创建一些看起来像vertex@23f7d873的东西并且实际上不会尝试遍历结构并打印出所有内容,所以这在直接意义上解决了问题,但只有在我对要打印到屏幕上的内容非常小心。 I'd like to be able to call (println my-graph) have it print the ref as some type of custom text (possibly involving str ), and the other non-ref stuff normally.我希望能够调用(println my-graph)让它打印ref作为某种类型的自定义文本(可能涉及str ),以及其他非 ref 内容。

Currently I have a custom print function that prints each element of the struct on its own and completely skips printing the ref .目前我有一个自定义打印 function,它单独打印结构的每个元素并完全跳过打印ref (It turns out that looking at vertex@23f7d873 is not actually very useful). (事实证明,查看vertex@23f7d873实际上并不是很有用)。 This is awkward to use and hinders greatly doing casual inspection of stuff at the REPL and also prevents Emacs inspector from looking at stuff while I'm in a swank.core/break debug thingy.这使用起来很尴尬,并且极大地阻碍了在 REPL 中对内容进行随意检查,并且还阻止了 Emacs 检查员在我进行swank.core/break调试时查看内容。

One detail is the ref is actually a value in a defstruct that also contains some other stuff which I am trying to print normally.一个细节是ref实际上是defstruct中的一个值,它还包含一些我试图正常打印的其他内容。

So I'm wondering what path I should go down.所以我想知道我应该把 go 放在什么路径下。 I see these options:我看到这些选项:

  1. Figure out extend-type and apply the CharSequence protocol to my defstruct ed structure so that when it comes across a ref it works properly.找出extend-type并将CharSequence协议应用于我的defstruct ed 结构,以便在遇到ref时它可以正常工作。 This still requires a field-by-field inspection of the struct and a special case when it comes to the ref , but at least it localizes the problem to the struct and not to anything that contains the struct.这仍然需要逐个字段检查结构和涉及ref的特殊情况,但至少它将问题定位到结构而不是包含结构的任何内容。
  2. Figure out how to override the CharSequence protocol when it comes across a ref .找出在遇到ref时如何覆盖CharSequence协议。 This allows even more localized behavior and allows me to view a cyclic ref at the REPL even when it's not inside a struct.这允许更多的本地化行为,并允许我在 REPL 中查看循环引用,即使它不在结构内也是如此。 This is my preferred option.这是我的首选。
  3. Figure out how to do something with toString which I believe is called at some level when I do println .弄清楚如何使用toString做某事,我相信当我执行println时,它会在某种程度上被调用。 I'm most ignorant about this option.我对这个选项最无知。 Pretty ignorant about the other ones too, but I've been reading Joy of Clojure and I'm all inspired now.对其他人也一无所知,但我一直在阅读Joy of Clojure ,现在我都受到了启发。

Likewise this solution should apply to print and pprint and anything else that would normally barf when trying to print a cyclic ref.同样,此解决方案应适用于printpprint以及在尝试打印循环引用时通常会出现的任何其他问题。 What strategy should I employ?我应该采用什么策略?

thanks a lot for any input.非常感谢任何输入。

What you want to do is create a new namespace and define your own print functions that models the way clojure prints objects, and defaults to clojure's methods.你想要做的是创建一个新的命名空间并定义你自己的打印函数,模拟 clojure 打印对象的方式,并默认为 clojure 的方法。

In detail:详细地:

    Create an ns excluding pr-str and print-method. 创建一个不包括 pr-str 和 print-method 的 ns。 Create a multimethod print-method (copy exactly what is in clojure.core) Create a default method that simply delegates to clojure.core/print-method Create a method for clojure.lang.Ref that doesn't recursively print everything 创建一个多方法打印方法(完全复制 clojure.core 中的内容) 创建一个简单委托给 clojure.core/print-method 的默认方法 创建一个不递归打印所有内容的 clojure.lang.Ref 方法

As a bonus, if you are using clojure 1.4.0, you can use tagged literals so that reading in the output is also possible.作为奖励,如果您使用的是 clojure 1.4.0,则可以使用标记文字,以便也可以读取 output。 You would need to override the *data-readers* map for a custom tag and a function that returns a ref object. You'd also need to override the read-string behavior to ensure binding is called for *data-readers* .您需要覆盖自定义标记的*data-readers* map 和返回 ref object 的 function。您还需要覆盖读取字符串行为以确保为*data-readers*调用绑定。

I've provided an example for java.io.File我已经为 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)))

Now you can call like so (println (my.print/pr-str circular-data-structure))现在你可以这样调用(println (my.print/pr-str circular-data-structure))

I found my solution -- just create a multimethod which overloads clojure.core/print-method for each particular type, and call the generic function from within the multimethod.我找到了我的解决方案——只需创建一个为每个特定类型重载clojure.core/print-method的多方法,并从多方法中调用通用 function。 In this case, each multimethod calls the generic print-graph-element which knows what to do with references (it just prints out the hash of the referenced object rather than trying to print out the value of the referenced object).在这种情况下,每个 multimethod 调用知道如何处理引用的通用print-graph-element (它只是打印出引用的 object 的 hash,而不是尝试打印出引用对象的值)。 In this case I'm assuming the dispatch function of print-method is just (type <thing>) so it dispatches on type, and that's how I'm writing the multimethod.在这种情况下,我假设 print-method 的调度 function 只是(type <thing>)所以它按类型调度,这就是我编写 multimethod 的方式。 I actually couldn't find any documentation that that's how the print-method dispatch works, but it sure seems to behave that way.我实际上找不到任何文档说明打印方法调度是如何工作的,但它确实看起来是那样的。

This solution allows me to just type the name of the object, such as v0 (which was cerated by (make-vertex) in the repl and print-method gets called by the repl, thus preventing my StackOverflow error.该解决方案允许我仅键入 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))

If you just want to be able to print data structures with loops, try setting *print-level* to limit how deep the printer will descend.如果您只想能够打印带有循环的数据结构,请尝试设置*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: #>>>

There is also a *print-length* var which can be useful when you are dealing with data with infinite length.还有一个*print-length*变量,当你处理无限长度的数据时它会很有用。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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