Subject: bug#71684: [PATCH] doc: Document the peek and pk procedures.

From: Juliana Sims <[email-masked]>
Date: Thu, 20 Jun 2024 14:54:15 -0400

* doc/ref/api-debug.texi: Document the peek and pk procedures.
---
 doc/ref/api-debug.texi | 187 +++++++++++++++++++++++++++++++++++++++--
 1 file changed, 179 insertions(+), 8 deletions(-)

diff --git a/doc/ref/api-debug.texi b/doc/ref/api-debug.texi
index faa0c40bd..486473cdb 100644
--- a/doc/ref/api-debug.texi
+++ b/doc/ref/api-debug.texi
@@ -1,27 +1,198 @@
 @c -*-texinfo-*-
 @c This is part of the GNU Guile Reference Manual.
-@c Copyright (C)  1996, 1997, 2000, 2001, 2002, 2003, 2004, 2007, 2010, 2011, 2012, 2013, 2014, 2018, 2021
+@c Copyright (C)  1996-1997, 2000-2004, 2007, 2010-2014, 2018, 2021, 2024
 @c   Free Software Foundation, Inc.
 @c See the file guile.texi for copying conditions.
 
 @node Debugging
 @section Debugging Infrastructure
 
-@cindex Debugging
-In order to understand Guile's debugging facilities, you first need to
-understand a little about how Guile represents the Scheme control stack.
-With that in place we explain the low level trap calls that the virtual
-machine can be configured to make, and the trap and breakpoint
-infrastructure that builds on top of those calls.
+@cindex debugging
+Guile provides facilities for simple print-based debugging as well as
+more advanced debugging features. In order to understand Guile's
+advanced debugging facilities, one first must understand a little about
+how Guile represents the Scheme control stack. With that in place, we
+can explain the low level trap calls that the virtual machine can be
+configured to make, and the trap and breakpoint infrastructure that
+builds on top of those calls.
 
 @menu
+* Simple Debugging::            Print-based debugging.
 * Evaluation Model::            Evaluation and the Scheme stack.
 * Source Properties::           From expressions to source locations.
-* Programmatic Error Handling::  Debugging when an error occurs.
+* Programmatic Error Handling:: Debugging when an error occurs.
 * Traps::                       Breakpoints, tracepoints, oh my!
 * GDB Support::                 C-level debugging with GDB.
 @end menu
 
+
+@node Simple Debugging
+@subsection Simple Debugging
+
+Guile offers powerful tools for introspection and debugging at the REPL,
+covered in the rest of this section and elsewhere in this manual
+(@pxref{Interactive Debugging}). Here we deal with a more primitive
+approach, commonly called ``print debugging.'' Let's be honest: for most
+of us, this is our first line of debugging. And Guile doesn't judge us
+for it! Instead, Guile provides a powerful and convenient tool to
+facilitate print debugging: the @code{peek} procedure, more commonly
+known as @code{pk} (pronounced by naming the letters).
+
+@deffn {Scheme Procedure} peek stuff @dots{}
+@deffnx {Scheme Procedure} pk stuff @dots{}
+Print @var{stuff} to the current output port using @code{write}. Return
+the last argument.
+@end deffn
+
+@code{pk} allows us to look at the state of our code as it runs without
+having to step through it or break the normal code flow. Let's take a
+look at how one might use it. Let's say we have a procedure to make a
+smore, perhaps as part of a mod for a cozy space exploration game.
+
+@lisp
+(define (make-smore marshmallow graham-crackers chocolate fire)
+  "Toast @var{mashmallow} over @var{fire} then sandwich it and
+@var{chocolate} between @var{graham-crackers}."
+  (let ((toasted-marshmallow
+         (toast marshmallow fire)))
+    (unless (or (burned? toasted-marshmallow)
+                (undercooked? toasted-marshmallow))
+      (cons (car graham-crackers)
+            (cons toasted-marshmallow
+                  (cons chocolate
+                        (cons (cdr graham-crackers) '())))))))
+@end lisp
+
+We've run this procedure a few times, and it isn't doing what we expect.
+Instead of getting a tasty smore, we get nothing. Let's use @code{pk} to
+find out what's going on.
+
+@lisp
+(pk (make-smore (grab-one marshmallow-bag)
+                (cons graham-cracker graham-cracker)
+                campfire))
+
+;;; (#<unspecified>)
+@end lisp
+
+@code{#<unspecified>} is a value in Guile which indicates that no Scheme
+standard specifies a return value for whatever is returning it. In this
+case, it probably means that our @code{unless} check is not proving
+true, so the procedure returns nothing. Let's add a @code{pk} around the
+call to @code{toast} and see what happens.
+
+@lisp
+(define (make-smore marshmallow graham-crackers chocolate fire)
+  "Toast @var{mashmallow} over @var{fire} then sandwich it and
+@var{chocolate} between @var{graham-crackers}."
+  (let ((toasted-marshmallow
+         ;; Let's see what state the toasted-marshmallow is in
+         (pk 'toasted-marshmallow (toast marshmallow fire))))
+    (unless (or (burned? toasted-marshmallow)
+                (undercooked? toasted-marshmallow))
+      (cons (car graham-crackers)
+            (cons toasted-marshmallow
+                  (cons chocolate
+                        (cons (cdr graham-crackers) '())))))))
+
+(make-smore (grab-one marshmallow-bag)
+            (cons graham-cracker graham-cracker)
+            campfire)
+
+;;; (toasted-marshmallow #<<marshmallow> state: raw>)
+@end lisp
+
+Our marshmallow isn't getting cooked at all! Let's see if we can find
+out why. We'll check on the state of @var{fire} since we know that
+@code{toast} just operates on the state of the fire and of the
+marshmallow. @code{toasted-marshmallow} matches the state we expect for
+a fresh marshmallow, so the problem is probably with the fire.
+
+@lisp
+(define (make-smore marshmallow graham-crackers chocolate fire)
+  "Toast @var{mashmallow} over @var{fire} then sandwich it and
+@var{chocolate} between @var{graham-crackers}."
+  (let ((toasted-marshmallow
+         ;; Now we'll check on the fire, too
+         (pk 'toasted-marshmallow (toast marshmallow (pk 'fire fire)))))
+    (unless (or (burned? toasted-marshmallow)
+                (undercooked? toasted-marshmallow))
+      (cons (car graham-crackers)
+            (cons toasted-marshmallow
+                  (cons chocolate
+                        (cons (cdr graham-crackers) '())))))))
+
+(make-smore (grab-one marshmallow-bag)
+            (cons graham-cracker graham-cracker)
+            campfire)
+
+;;; (fire #<<fire> state: unlit>)
+
+;;; (toasted-marshmallow #<<marshmallow> state: raw>)
+@end lisp
+
+Oh, well that makes sense! A fire can't cook a marshmallow if it isn't
+lit!
+
+Notice that the result of evaluating the @code{pk} around @code{fire} is
+printed before the one around @code{toast}. This is just the result of
+the normal process of evaluating s-expressions from the inside out. We
+highlight it because it can be confusing at first, especially with more
+@code{pk}s in more complex code.
+
+Let's add a guard to light the fire and run our procedure again.
+
+@lisp
+(define (make-smore marshmallow graham-crackers chocolate fire)
+  "Toast @var{mashmallow} over @var{fire} then sandwich it and
+@var{chocolate} between @var{graham-crackers}."
+  (let ((toasted-marshmallow
+         (toast marshmallow fire)))
+    (unless (lit? fire)
+      (light fire))
+    (unless (or (burned? toasted-marshmallow)
+                (undercooked? toasted-marshmallow))
+      (cons (car graham-crackers)
+            (cons toasted-marshmallow
+                  (cons chocolate
+                        (cons (cdr graham-crackers) '())))))))
+
+(make-smore (grab-one marshmallow-bag)
+            (cons graham-cracker graham-cracker)
+            campfire)
+@result{} (#<<graham-cracker>> #<<marshmallow> state: cooked> #<<chocolate>> #<<graham-cracker>>)
+@end lisp
+
+Yay! Now it works, and we have a tasty smore!
+
+As we demonstrated, you can pass in any number of arguments and the
+result of evaluating the last argument is the value returned from
+@code{pk}. This is handy to, as we showed, wrap code in-line without
+needing to add extra steps along the way while still providing
+informative labels about what, exactly, is getting printed. We could as
+easily have put @code{pk}s completely on their own, rather than wrapping
+other code. This is commonly used to, for example, test if a given
+procedure or part of a procedure is entered. Earlier, we could have put
+a @code{pk} in the body of the @code{unless} clause to let us know if we
+entered it, such as:
+
+@lisp
+(define (make-smore ...)
+  ...
+  (unless ...
+    (pk 'inside-unless)
+    ...))
+@end lisp
+
+As a final note, labels don't have to be symbols. @code{pk} will happily
+print any object we pass it. We could have used strings or anything else
+we wanted alongside the code we were interested in.
+
+Hopefully this silly little example has shown the utility of @code{pk}.
+Now that it's in your toolbox, go forth, newly empowered, and happy
+hacking!
+
+
 @node Evaluation Model
 @subsection Evaluation and the Scheme Stack
 
-- 
2.45.1