(Pretty) Print large objects in Common Lisp
Asked Answered
T

1

6

The problem generally appears if I have a class containing, for example, a couple of slots that would be filled with vectors. If I want to make the object of this class more-or-less transparent, I implement print-object for it. And here I am faced with the problem:

  1. If I print everything in one line, REPL's heuristics are not good enough to determine how to arrange printable parts in multiple lines, causing everything to be shifted to the right (see example below).
  2. If I decide to split the output into multiple lines manually, I have a problem of how to indent everything properly, such that if this object is a part of another object, indentation is preserved (see example below for more clarity).

Here is the code. Consider two classes:

(defclass foo ()
  ((slot1 :initarg :slot1)
   (slot2 :initarg :slot2)))

(defclass bar ()
  ((foo-slot :initarg :foo)))

And I have the following instances:

(defparameter *foo*
  (make-instance 'foo
    :slot1 '(a b c d e f g h i j k l m n o p q r s t u v)
    :slot2 #(1 2 3 4 5 6 7 8)))


(defparameter *bar*
  (make-instance 'bar
    :foo *foo*))

What I want to see, is something like this:

> *bar*
#<BAR
  foo-slot = #<FOO
                slot1 = (A B C D E F G H I J K L M N O P Q R S T U V)
                slot2 = #(1 2 3 4 5 6 7 8)>>

Case 1: Printing everything in one line

Definitions of print-object for these classes can be something like these:

(defmethod print-object ((obj foo) out)
  (with-slots (slot1 slot2) obj
    (print-unreadable-object (obj out :type t)
      (format out "slot1 = ~A slot2 = ~A" slot1 slot2))))

(defmethod print-object ((obj bar) out)
  (with-slots (foo-slot) obj
    (print-unreadable-object (obj out :type t)
      (format out "foo-slot = ~A" foo-slot))))

However, their printable representation is less than ideal:

> *foo*
#<FOO slot1 = (A B C D E F G H I J K L M N O P Q R S T U V) slot2 = #(1 2 3 4 5
                                                                      6 7 8)>

> *bar*
#<BAR foo-slot = #<FOO slot1 = (A B C D E F G H I J K L M N O P Q R S T U V) slot2 = #(1
                                                                                       2
                                                                                       3
                                                                                       4
                                                                                       5
                                                                                       6
                                                                                       7
                                                                                       8)>>

Case 2: Attempt to multiple line printing

Using multiple line printing, I don't know how to control indentation:

(defmethod print-object ((obj foo) out)
  (with-slots (slot1 slot2) obj
    (print-unreadable-object (obj out :type t)
      (format out "~%~Tslot1 = ~A~%~Tslot2 = ~A" slot1 slot2))))

(defmethod print-object ((obj bar) out)
  (with-slots (foo-slot) obj
    (print-unreadable-object (obj out :type t)
      (format out "~%~Tfoo-slot = ~A" foo-slot))))

Thus, *foo* prints OK, but *bar* isn't:

> *foo*
#<FOO 
  slot1 = (A B C D E F G H I J K L M N O P Q R S T U V)
  slot2 = #(1 2 3 4 5 6 7 8)>
*bar*
#<BAR 
  foo-slot = #<FOO 
  slot1 = (A B C D E F G H I J K L M N O P Q R S T U V)
  slot2 = #(1 2 3 4 5 6 7 8)>>

In the past I tried to play around with print-indent, but without a success (I couldn't see any effect from it, maybe wasn't using it correctly, SBCL 1.2.14).

Is there a (preferably simple) way to solve this?

Teutonize answered 20/1, 2016 at 9:41 Comment(0)
R
8

Try something like this (might require more polishing):

(defmethod print-object ((obj foo) out)
  (with-slots (slot1 slot2) obj
    (print-unreadable-object (obj out :type t)
      (format out "~<~:_slot1 = ~A ~:_slot2 = ~A~:>" (list slot1 slot2)))))

(defmethod print-object ((obj bar) out)
  (with-slots (foo-slot) obj
    (print-unreadable-object (obj out :type t)
      (format out "~<~:_foo-slot = ~A~:>" (list foo-slot)))))

It uses ~< and ~:>, which are format operations for logical blocks. Then it uses ~:_, which is a conditional newline. You should read the relevant hyperspec section.

Root answered 20/1, 2016 at 10:51 Comment(1)
Yes, this does it! Thanks! Couple of polishes: one can use ~@< at the beginning of the logical block and then no need to put arguments into list. And it is also possible to create a mandatory newline (respecting indentation) with ~:@_ directive.Teutonize

© 2022 - 2024 — McMap. All rights reserved.