最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

formatting - How to use the format control arguments ~< ~> ~I correctly for a tree-like set of clos objects from w

programmeradmin3浏览0评论

I think I did this right in the past, but this time, I am kind of stuck... The code below is the CLOS style structure of a rope. Which in essence is a binary tree. For printing purposes, I would like the left and right properties to recursively indent by 2 spaces.

(defclass rope ()
  ((depth :type fixnum :initform 0 :initarg :depth :reader rope-depth)
   (weight :type fixnum :initform 0 :initarg :weight :reader rope-weight)))

(defclass rope-leaf (rope)
  ((string :type string :initarg :string :reader rope-string)))

(defclass rope-node (rope)
  ((left :type rope  :initarg :left :reader rope-left)
   (right :type rope :initarg :right :reader rope-right)))

(defmethod print-object ((object rope) stream)
  (print-unreadable-object (object stream :type t)
    (with-slots (depth weight) object
      (format stream "depth: ~a weight: ~a" depth weight))))

(defmethod print-object ((object rope-node) stream)
  (call-next-method)
  (with-slots (left right) object
    (format stream "~<~%left: ~a ~%right: ~a~>" left right)))

(defmethod print-object ((object rope-leaf) stream)
  (print-unreadable-object (object stream :type t)
    (call-next-method)
    (format stream " ~s" (rope-string object))))

The problem is with the print-objectimplementation for rope-node.

I read the documentation, but could not find an intuitive example for nested (recursive) structures with a tree-like indent. I tinkered for a (longer while), trying myself. Eventually, I asked googles Gemini AI for help, but it passed with:

It seems I'm having trouble correctly applying these specific format control directives within the print-object context. I'm still under development and learning to handle these nuances of Common Lisp's format function.

Now is the time for humanity to shine ;) Who can give a solution and explain, how such nested prints are supposed to work?

I think I did this right in the past, but this time, I am kind of stuck... The code below is the CLOS style structure of a rope. Which in essence is a binary tree. For printing purposes, I would like the left and right properties to recursively indent by 2 spaces.

(defclass rope ()
  ((depth :type fixnum :initform 0 :initarg :depth :reader rope-depth)
   (weight :type fixnum :initform 0 :initarg :weight :reader rope-weight)))

(defclass rope-leaf (rope)
  ((string :type string :initarg :string :reader rope-string)))

(defclass rope-node (rope)
  ((left :type rope  :initarg :left :reader rope-left)
   (right :type rope :initarg :right :reader rope-right)))

(defmethod print-object ((object rope) stream)
  (print-unreadable-object (object stream :type t)
    (with-slots (depth weight) object
      (format stream "depth: ~a weight: ~a" depth weight))))

(defmethod print-object ((object rope-node) stream)
  (call-next-method)
  (with-slots (left right) object
    (format stream "~<~%left: ~a ~%right: ~a~>" left right)))

(defmethod print-object ((object rope-leaf) stream)
  (print-unreadable-object (object stream :type t)
    (call-next-method)
    (format stream " ~s" (rope-string object))))

The problem is with the print-objectimplementation for rope-node.

I read the documentation, but could not find an intuitive example for nested (recursive) structures with a tree-like indent. I tinkered for a (longer while), trying myself. Eventually, I asked googles Gemini AI for help, but it passed with:

It seems I'm having trouble correctly applying these specific format control directives within the print-object context. I'm still under development and learning to handle these nuances of Common Lisp's format function.

Now is the time for humanity to shine ;) Who can give a solution and explain, how such nested prints are supposed to work?

Share Improve this question edited Feb 14 at 16:00 Barmar 782k56 gold badges546 silver badges660 bronze badges asked Feb 14 at 15:50 BitTicklerBitTickler 11.9k6 gold badges36 silver badges59 bronze badges 7
  • How ironic that an AI has trouble programming in a language created for AI research. – Barmar Commented Feb 14 at 15:59
  • @Barmar Lisp was used by first AI wave, the same way, the second AI wave now uses Python. – BitTickler Commented Feb 14 at 16:01
  • As a first point, you need to use ~:> to end the directive to make this be logical blocks. If you end with ~> it's the justification operation. – Barmar Commented Feb 14 at 16:05
  • and then turn it into 1 argument instead of 2? (format stream "..." (list left right)) as it seems to complain then about too many arguments. Or do I need 2 logical blocks? – BitTickler Commented Feb 14 at 16:08
  • You can use ~@< to make it process the rest of the arguments. – Barmar Commented Feb 14 at 16:10
 |  Show 2 more comments

1 Answer 1

Reset to default 2

Example tree

Let's first define constructors and have an example tree:

(defun node (a b)
  (make-instance 'rope-node :left a :right b))

(defun leaf (s)
  (make-instance 'rope-leaf :string s))

(defparameter *test*
  (node (node (leaf "a")
              (leaf "b"))
        (node (leaf "c")
              (node (leaf "d")
                    (leaf "e")))))

Explicit formatter

When I am in doubt about the FORMAT function, I try first to use the pprint-logical-block facility. Here I'm building a list of children as a second argument to the form, so that I can iterate over them. Using NIL instead and accessing them explicitly is another possible approach. But using another object, like o, instead of a list, would bypass the block completely (that used to be the confusing part to me).

(defmethod print-object ((o rope-node) stream)
  (pprint-logical-block (stream (list (rope-left o) (rope-right o)))
    (pprint-indent :block 2 stream)
    (format stream "#<rope-node")
    (loop
      (pprint-exit-if-list-exhausted)
      (format stream " ")
      (pprint-newline :linear stream)
      (print-object (pprint-pop) stream)))
  (format stream ">"))

I also tried to combine it with print-unreadable-object but the function adds its own indent, which obscures things a little bit. Here I define explicitly the indent, before starting writing anything, so that indent starts at the position of the #< sequence of characters. Thanks to the list of children, I can use pprint-pop and pprint-exit-if-list-exhausted to iterate on them. Notice how I print both a space and a newline (which removes the space if needed) when I know that there is at least one remaining element. The linear style of newline helps printing either all child nodes on the same line, or each on a distinct line.

With the above definition (print *test*) outputs the following:

#<rope-node
  #<rope-node #<ROPE-LEAF {1021F704E3}> #<ROPE-LEAF {1021F704F3}>>
  #<rope-node
    #<ROPE-LEAF {1021F70503}>
    #<rope-node #<ROPE-LEAF {10222B0503}> #<ROPE-LEAF {10222B0513}>>>>

I can also define a method for class rope-leaf:

(defmethod print-object ((o rope-leaf) stream)
  (format stream "[~a]" (rope-string o)))

Now, the output fits in a single line:

#<rope-node #<rope-node [a] [b]> #<rope-node [c] #<rope-node [d] [e]>>> 

Using FORMAT

Now that I know the structure of my formatter a little better, it should be easy to translate it to the character soup dedicated syntax of format, let's try:

(defmethod print-object ((o rope-node) stream)
  (format stream
          "~@<~2I#<node~{~^ ~_~a~}~:>>"
          (list (rope-left o) (rope-right o))))

With some trial and errors, the above is similar to the previous definition, you have to remember that ~<...~:> accepts a list of arguments, but that you can use ~@<...~:> to pass the rest of the arguments given to FORMAT instead. Inside the block, I also need to iterate over a list of children using ~{...~}, which is why I build a list. But, the ~@{...~} syntax also allows you to take the next available arguments, meaning that the following works too:

(defmethod print-object ((o rope-node) stream)
  (format stream
          "~@<~2I#<node~@{~^ ~_~a~}~:>>"
          (rope-left o)
          (rope-right o)))

The example above prints as:

#<node #<node [a] [b]> #<node [c] #<node [d] [e]>>> 

Readability

Let's just tweak the printers a bit:

(defmethod print-object ((o rope-leaf) stream)
  (with-slots ((s string)) o
    (format stream "(~s ~s)" 'leaf s)))

(defmethod print-object ((o rope-node) stream)
  (with-slots ((L left) (R right)) o
    (format stream "~@<~2I(~s~@{~^ ~_~a~}~:>)" 'node L R)))

I switch to with-slots with the binding syntax where the slots are given shorter names, and I am emitting a Lisp form as an output. That makes the output readable, and since the node and leaf symbols are written using ~s, they are going to be prefixed by the home package if necessary.

(let ((*print-right-margin* 20))
  (progn (print *test*) (values)))

(node
  (node
    (leaf "a")
    (leaf "b"))
  (node
    (leaf "c")
    (node
      (leaf "d")
      (leaf "e"))))

The default printer for lists wouldn't respect the formatting you may want to implement, but if you don't care too much its usually enough to convert to a readable list when printing, like this:

(defmethod print-object ((o rope-leaf) stream)
  (print-object `(leaf ,(rope-string o)) stream))

(defmethod print-object ((o rope-node) stream)
  (print-object `(node ,(rope-left o) ,(rope-right o)) stream))
发布评论

评论列表(0)

  1. 暂无评论