Re: defstruct print function

Richard M Kreuter via Sbcl-help <[email protected]>
Newsgroups gmane.lisp.steel-bank.general
Message-ID <[email protected]>
steve gonedes writes:

> I am having some trouble using the print-function with defstruct. I 
> cannot remember how to handle the warnings. if one could point me to the 
> manual. any ideas - run into this problem often.

Hi Steve,

tl;dr (1) don't worry about it, (2) use the interpreter, (3) tell the
compiler more about what you're doing, (4) compile your code in file,
(5) load your code from a file.

(For clarity:

- Each of your two forms refers to a function defined by the other (the
  definition of PRINT-JTMS refers to JTMS-TITLE, and structure
  definition for JTMS refers to PRINT-JTMS). So there's an
  interdependency between the forms.

- SBCL's EVAL invokes the compiler by default. In the context of the top
  level loop, when the evaluator calls the compiler on a form, the form
  is compiled "separately" and using only the knowledge the compiler has
  accumulated so far in the Lisp session.

- SBCL implicitly proclaims DEFSTRUCT accessors as inlinable. When a
  function is proclaimed inline, the compiler tries to be helpful by
  letting the user know if there were previously-compiled calls that
  weren't inlined.

I believe these explain the style warnings you've observed.)

Below are several options. Each transcript uses a fresh SBCL session in
order to demonstrate the effects under the compiler's initial knowledge
of your structure & printer function (i.e., none).

(1) Don't worry about it until/unless you have to. (What not to worry
about and when you might have to are hard to characterize, though.)

(2) If this sort of REPL interaction matters to you, you might configure
EVAL to use the interpreter instead of the compiler. (But note that this
controls EVAL, not your REPL per se. So LOAD on a source file will also
use the interpreter, which might not be what you want. You could rebind
the relevant special around LOAD if that matters. Or maybe there's a way
to control what function SLIME's REPL calls as its evaluator; IDK.)

--
$ sbcl --noinform --no-userinit 
* (setq sb-ext:*evaluator-mode* :interpret)
:INTERPRET
* (defun print-jtms (jtms stream ignore)
    (declare (ignore ignore))
    (format stream "#<JTMS: ~A>" (jtms-title jtms)))
PRINT-JTMS
* (defstruct (jtms (:copier nil) (:print-function print-jtms))
    (title nil)
    (node-counter 0)             ;; unique namer for nodes.
    (just-counter 0)             ;; unique namer for justifications.
    (nodes nil)                  ;; list of all tms nodes.
    (justs nil)                  ;; list of all justifications
    (debugging nil)              ;; debugging flag
    (contradictions nil)         ;; list of contradiction nodes.
    (assumptions nil)            ;; list of assumption nodes.
    (checking-contradictions T)  ;; For external systems
    (node-string nil)
    (contradiction-handler nil)
    (enqueue-procedure nil))
JTMS
* (make-jtms)
#<JTMS: NIL>
--

(3) Tell the compiler more about what you're doing.

(3a) Inform the compiler that JTMS-TITLE will be a function, and that
you don't want it inlined in the print function

--
$ sbcl --noinform --no-userinit 
* (declaim (ftype function jtms-title))
(JTMS-TITLE)
* (defun print-jtms (jtms stream ignore)
    (declare (ignore ignore))
    (locally (declare (notinline jtms-title))
      (format stream "#<JTMS: ~A>" (jtms-title jtms))))
PRINT-JTMS
* (defstruct (jtms (:copier nil) (:print-function print-jtms))
    (title nil)
    (node-counter 0)             ;; unique namer for nodes.
    (just-counter 0)             ;; unique namer for justifications.
    (nodes nil)                  ;; list of all tms nodes.
    (justs nil)                  ;; list of all justifications
    (debugging nil)              ;; debugging flag
    (contradictions nil)         ;; list of contradiction nodes.
    (assumptions nil)            ;; list of assumption nodes.
    (checking-contradictions T)  ;; For external systems
    (node-string nil)
    (contradiction-handler nil)
    (enqueue-procedure nil))
JTMS
* (make-jtms)
#<JTMS: NIL>
--

(3b) Inform the compiler that PRINT-JTMS will be a function and define
the structure first

--
$ sbcl --noinform --no-userinit 
* (declaim (ftype function print-jtms))
(PRINT-JTMS)
* (defstruct (jtms (:copier nil) (:print-function print-jtms))
    (title nil)
    (node-counter 0)             ;; unique namer for nodes.
    (just-counter 0)             ;; unique namer for justifications.
    (nodes nil)                  ;; list of all tms nodes.
    (justs nil)                  ;; list of all justifications
    (debugging nil)              ;; debugging flag
    (contradictions nil)         ;; list of contradiction nodes.
    (assumptions nil)            ;; list of assumption nodes.
    (checking-contradictions T)  ;; For external systems
    (node-string nil)
    (contradiction-handler nil)
    (enqueue-procedure nil))
JTMS
* (defun print-jtms (jtms stream ignore)
    (declare (ignore ignore))
    (format stream "#<JTMS: ~A>" (jtms-title jtms)))
PRINT-JTMS
* (make-jtms)
#<JTMS: NIL>
--

(4) The file compiler is allowed & able to consider a file's forms
together as a unit. So it's possible to compile a file containing your 2
forms without any style warnings:

(4a) Put the structure definition first if you care about inlining the
accessor:

--
$ cat foo.lisp
(in-package "CL-USER")

(defstruct (jtms (:copier nil) (:print-function print-jtms))
  (title nil)
  (node-counter 0)             ;; unique namer for nodes.
  (just-counter 0)             ;; unique namer for justifications.
  (nodes nil)                  ;; list of all tms nodes.
  (justs nil)                  ;; list of all justifications
  (debugging nil)              ;; debugging flag
  (contradictions nil)         ;; list of contradiction nodes.
  (assumptions nil)            ;; list of assumption nodes.
  (checking-contradictions T)  ;; For external systems
  (node-string nil)
  (contradiction-handler nil)
  (enqueue-procedure nil))

(defun print-jtms (jtms stream ignore)
  (declare (ignore ignore))
  (format stream "#<JTMS: ~A>" (jtms-title jtms)))
$ sbcl --noinform --no-userinit
* (compile-file "foo.lisp")
; compiling file "/home/me/foo.lisp" (written 14 AUG 2024 09:06:27 AM):

; wrote /home/me/foo.fasl
; compilation finished in 0:00:00.038
#P"/home/me/foo.fasl"
NIL
NIL
* (load *)
T
* (make-jtms)
#<JTMS: NIL>
--

(4b) Use the recently-restored block compilation feature:

--
$ cat bar.lisp
(in-package "CL-USER")

(defun print-jtms (jtms stream ignore)
  (declare (ignore ignore))
  (format stream "#<JTMS: ~A>" (jtms-title jtms)))

(defstruct (jtms (:copier nil) (:print-function print-jtms))
  (title nil)
  (node-counter 0)             ;; unique namer for nodes.
  (just-counter 0)             ;; unique namer for justifications.
  (nodes nil)                  ;; list of all tms nodes.
  (justs nil)                  ;; list of all justifications
  (debugging nil)              ;; debugging flag
  (contradictions nil)         ;; list of contradiction nodes.
  (assumptions nil)            ;; list of assumption nodes.
  (checking-contradictions T)  ;; For external systems
  (node-string nil)
  (contradiction-handler nil)
  (enqueue-procedure nil))
$ sbcl --noinform --no-userinit
* (compile-file "bar.lisp" :block-compile t)
; compiling file "/home/me/bar.lisp" (written 14 AUG 2024 09:11:51 AM):

; wrote /home/me/bar.fasl
; compilation finished in 0:00:00.033
#P"/home/me/bar.fasl"
NIL
NIL
* (load *)
T
* (make-jtms)
#<JTMS: NIL>
--

(5) Even without compiling a source file, LOAD can be made quieter by
deferring style warnings using WITH-COMPILATION-UNIT. This transcript
uses the same foo.lisp as in (4a), with the structure definition before
the definition of PRINT-JTMS:

--
$ sbcl --noinform --no-userinit
* (with-compilation-unit () (load "foo.lisp"))
T
* (make-jtms)
#<JTMS: NIL>
--

Finally, I suspect that most folks nowadays probably load most of their
code from files first, and interact with Lisp at a REPL only afterward.
Because that workflow teaches the compiler about a codebase, users who
work that way likely won't encounter style warnings for these sorts of
"bootstrapping" reasons very often. IOW, when such users do see style
warnings, e.g., about undefined functions, they're a bit more salient.

Hope some of those help,
Richard
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.