Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

lisp: dynamic scope vs explicit parameter passing

I see two different patterns for "output" functions in (common) lisp:

(defun implicit ()
  (format t "Life? Don't talk to me about life!"))

(defun explicit (stream)
  (format stream "This will all end in tears."))

(defun test-im-vs-ex-plicit ()
  (values
   (with-output-to-string (stream)
     (let ((*standard-output* stream))
       (implicit)))
   (with-output-to-string (stream)
     (explicit stream))))

Is using dynamic scope like in implicit considered bad practice or is this a generally accepted use of dynamic scoping? Note that I'm assuming this is for e.g. a DSL to build complex output, like HTML, SVG, Latex or whatever and is not expected to do anything different apart from producing a printed representation.

Are there - apart from style - any important differences, e.g. with respect to performance, concurrency, or whatever?

like image 799
Daniel Jour Avatar asked Sep 24 '26 09:09

Daniel Jour


2 Answers

Actually you can bind *standard-output* directly:

(defun test-im-vs-ex-plicit ()
  (values
   (with-output-to-string (*standard-output*)   ; here
     (implicit))
   (with-output-to-string (stream)
     (explicit stream))))

There is no real simple answer. My advice:

Use stream variables, this makes debugging easier. They appear on the argument lists and are easier to spot in the backtrace. Otherwise you would need to see in the backtrace where there is a dynamic rebinding of a stream variable.

a) Nothing to pass?

(defun print-me (&optional (stream *standard-output*))
  ...)

b) One or more fixed arg:

(defun print-me-and-you (me you &optional (stream *standard-output*))
  ...)

c) One or more fixed args and multiple optional args:

(defun print-me (me
                 &key
                 (style  *standard-style*)
                 (font   *standard-font*)
                 (stream *standard-output*))
  ...)

Note also this:

Now assume (implicit) has an error and we get a break loop, a debugging repl. What's the value of standard-output in this break loop?

CL-USER 4 > (defun test ()
              (flet ((implicit ()
                       (write-line "foo")
                       (cerror "go on" "just a break")
                       (write-line "bar")))
                (with-output-to-string (stream)
                  (let ((*standard-output* stream))
                    (implicit)))))
TEST

CL-USER 5 > (compile 'test)
TEST
NIL
NIL

CL-USER 6 > (test)

Error: just a break
  1 (continue) go on
  2 (abort) Return to level 0.
  3 Return to top loop level 0.

Type :b for backtrace or :c <option number> to proceed.
Type :bug-form "<subject>" for a bug report template or :? for other options.

CL-USER 7 : 1 > *standard-output*
#<SYSTEM::STRING-OUTPUT-STREAM 40E06AD80B>

CL-USER 8 : 1 > (write-line "baz")
"baz"

CL-USER 9 : 1 > :c 1
"foo
baz
bar
"

Above is what you see in LispWorks or SBCL. Here you have access to the real program's binding, but using output functions during debugging will have effects on this stream.

In other implementations *standard-output* will be rebound to actual terminal io - for example in Clozure CL and CLISP.

If your program does not rebind *standard-output* there is less confusion in those cases. If I write code, I often think about what would be more useful in a REPL environment - which is different from languages, where there is less interactive debugging on REPLs and break loops...

like image 110
Rainer Joswig Avatar answered Sep 28 '26 22:09

Rainer Joswig


I'm not a Lisp expert, but I've seen plenty of code using implicit values for *standard-output*. The argument from the lisp community is that this approach makes the code easier to run/test in the REPL (I come from a C/Java background so anything that smells of a global variable makes feel unease, but it's the lisp way).

About concurrency, each thread in CL has a different copy of *standard-output*, so your threads will be safe, but you need to configure them properly. You can read a bit more about this in the lisp cookbook - threads section.

like image 32
Augusto Avatar answered Sep 28 '26 22:09

Augusto