Customizing the printing of maps by :type in Cloju

2019-07-11 08:12发布

问题:

Pretty-printing this map comes out pretty ugly:

  {:type :move,
   :name :boost,
   :from
   {:nodeid :plus,
    :name :left-operand,
    :value
    {:args [:result :right-operand],
     :f
     #object[fargish.workspace_test$fn__159675$fn__159678 0xb3f518f "fargish.workspace_test$fn__159675$fn__159678@b3f518f"]},
    :dockclass :input,
    :ref [:plus :left-operand]},
   :to
   {:nodeid :source11,
    :name :output,
    :value 11,
    :dockclass :output,
    :ref [:source11 :output]},
   :do
   #object[fargish.workspace$do_boost 0x179d226e "fargish.workspace$do_boost@179d226e"],
   :do-hypothetically
   #object[fargish.workspace$do_hypothetical_boost 0x68d6475a "fargish.workspace$do_hypothetical_boost@68d6475a"]}

Most of the time, I don't need to see most of that. I'd like it to look something roughly like this:

#boost{:from [:plus :left-operand] :to [:source11 :output]}

Does Clojure provide a hook to let me insert code so that if the value of a map's :type key is :move, str will call a function I write to generate the string?

I've looked into print-method. If I understand it correctly, that would extend a multimethod that dispatches on the type of the argument, viz. clojure.lang.PersistentArrayMap, not the :type of the map. AFAIK, there's no way to "forward" to the previously defined multimethod, or else I could write a print-method for clojure.lang.PersistentArrayMap that looked at :type and then called the generic method if its value isn't :move.

Even just customizing pprint would be helpful. Theoretically, pprint is very customizable, but I haven't found documentation that explains how. This is incomplete and suggests that pprint isn't really done.

Affecting the output of pprint would be nice but it isn't strictly necessary. Just being able to affect str and print would be a great help.

回答1:

print-method already looks at the :type key on the metadata of your object. Just arrange for :type to be in the metadata rather than (or in addition to) the actual map itself, and you can define your own print-method.



回答2:

There is a pretty printing package called zprint which you can use that will get you very close to what you want, and still get you the benefits of pretty printing when your output is large and hard to visually comprehend. I've given some examples below:

; Basic zprint pretty-print of a similar map.  Note that it orders the keys
; alphabetically

user=> (zprint soq)
{:do #<Fn@4d0e1990 clojure.lang.PersistentList/Primordial>,
 :do-hypothetically #<Fn@4d0e1990 clojure.lang.PersistentList/Primordial>,
 :from {:dockclass :input,
        :name :left-operand,
        :nodeid :plus,
        :ref [:plus :left-operand],
        :value {:args [:result :right-operand],
                :f #<Fn@4d0e1990 clojure.lang.PersistentList/Primordial>}},
 :name :boost,
 :to {:dockclass :output,
      :name :output,
      :nodeid :source11,
      :ref [:source11 :output],
      :value 11},
 :type :move}

; You can order the keys so the ones you care about come in the order
; you want.

user=> (zprint soq {:map {:key-order [:name :from :to]}})
{:name :boost,
 :from {:name :left-operand,
        :dockclass :input,
        :nodeid :plus,
        :ref [:plus :left-operand],
        :value {:args [:result :right-operand],
                :f #<Fn@4d0e1990 clojure.lang.PersistentList/Primordial>}},
 :to {:name :output,
      :dockclass :output,
      :nodeid :source11,
      :ref [:source11 :output],
      :value 11},
 :do #<Fn@4d0e1990 clojure.lang.PersistentList/Primordial>,
 :do-hypothetically #<Fn@4d0e1990 clojure.lang.PersistentList/Primordial>,
 :type :move}

; You can get zprint to ignore keys completely

user=> (zprint soq {:map {:key-order [:name :from :to] :key-ignore-silent [[:from :name] :dockclass :nodeid :value :do :do-hypothetically :type [:to :name]]}})
{:name :boost,
 :from {:ref [:plus :left-operand]},
 :to {:ref [:source11 :output]}}

; You can configure zprint to do this all the time

user=> (zprint.core/set-options! {:map {:key-order [:name :from :to] :key-ignore-silent [[:from :name] :dockclass :nodeid :value :do :do-hypothetically :type [:to :name]]}})

; Now you just zprint it...

user=> (zprint soq)
{:name :boost,
 :from {:ref [:plus :left-operand]},
 :to {:ref [:source11 :output]}}

You can configure zprint with a ~/.zprintrc file to do this all the time.

You can also use czprint to get it colored, and you can color the keys any way you want too with a :key-color map. I find this useful when I look at a whole sequence of maps -- changing the color of one key and ordering the keys in the way I want allows me to comprehend a sequence of maps much more easily.

It would be simple to build a function to look at the :type and then call zprint with whatever options were appropriate for that type. Of course, the :ref is still in there, but the tradeoff comes when you have a lot of output, and the zprint pretty printing kicks in and it is just easier to read and understand.