Reputation: 16156
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?
Upvotes: 3
Views: 374
Reputation: 8135
The only statistically relevant pattern I see in Common Lisp itself, other than explicitly passing a stream, is an optional stream argument which defaults to *standard-input*
or *standard-output*
depending on the direction that the function requires.
The implicit cases in Common Lisp all deal with unspecified input/output, such as:
y-or-n-p
/yes-or-no-p
which use *query-io*
apropos
, disassemble
and room
which use *standard-output*
describe
which can use either *standard-output*
or *terminal-io*
trace
/untrace
and time
which use *trace-output*
dribble
which may bind *standard-input*
and/or *standard-output*
step
and inspect
may do whatever they want, from nothing, to a standard-input and standard-output command loop, to displaying a graphical tool window
So, I believe all other cases you might have seen are from libraries. My advise is to not follow any implicit pattern. However, one good exception is in HTML generators, which bind some variable e.g. *html-stream*
so that the macros that follow can refer to that variable without clutter. Imagine if you had to tell the stream on each macro (not a real example):
(html
(head (title "Foo"))
(body (p "This is just a simple example.")
(p "Even so, try to imagine this without an implicit variable.")))
For real examples, check out (at least) CL-WHO (with-html-output) and AllegroServe's HTML generator.
So, the advantage here is purely syntatic.
There is never a performance reason to use dynamic bindings. There might be a stack space reason, to avoid passing a stream as an argument, but this is a very weak reason, any existent recursion will just blow a little bit further.
Upvotes: 1
Reputation: 2671
I just wanted to add that one thing you can do in Common Lisp is combine the two practices:
(defun implicit (&optional (message "Life? Don't talk to me about life!"))
(format t message))
(defun explicit (*standard-output*)
(implicit "This will all end in tears."))
Since *standard-output*
is the name of the argument, calling explicit
with a stream argument automatically rebinds the dynamic variable *standard-output*
to the value of that argument.
Upvotes: 1
Reputation: 139251
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...
Upvotes: 5
Reputation: 29847
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.
Upvotes: 1