Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

(Pretty) Print large objects in Common Lisp

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?

like image 537
mobiuseng Avatar asked Jan 20 '16 09:01

mobiuseng


1 Answers

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.

like image 68
jkiiski Avatar answered Nov 11 '22 15:11

jkiiski