我在(常见)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))))
使用implicit
中的动态范围被认为是不好的做法,还是普遍接受的动态范围使用?请注意,我假设这是例如用于构建复杂输出的DSL,如HTML,SVG,Latex等等,除了生成打印表示外,预计不会做任何不同的事情。
除了风格之外,还有任何重要的差异,例如:在性能,并发性等方面?
答案 0 :(得分:5)
实际上你可以直接绑定(defun test-im-vs-ex-plicit ()
(values
(with-output-to-string (*standard-output*) ; here
(implicit))
(with-output-to-string (stream)
(explicit stream))))
:
(defun print-me (&optional (stream *standard-output*))
...)
没有真正简单的答案。我的建议:
使用流变量,这使调试更容易。它们出现在参数列表中,更容易在回溯中找到。否则,您需要在回溯中看到流变量的动态重新绑定。
a)什么都没有通过?
(defun print-me-and-you (me you &optional (stream *standard-output*))
...)
b)一个或多个固定的arg:
(defun print-me (me
&key
(style *standard-style*)
(font *standard-font*)
(stream *standard-output*))
...)
c)一个或多个固定的args和多个可选的args:
(implicit)
还请注意:
现在假设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
"
有错误,我们得到一个中断循环,一个调试 repl 。此中断循环中标准输出的价值是什么?
*standard-output*
以上是您在LispWorks或SBCL中看到的内容。在这里,您可以访问真实程序的绑定,但在调试期间使用输出函数会对此流产生影响。
在其他实现中,*standard-output*
将被反弹到实际的终端io - 例如在Clozure CL和CLISP中。
如果您的程序没有重新绑定{{1}},那么在这些情况下,混淆会更少。如果我编写代码,我经常考虑在REPL环境中哪些更有用 - 这与语言不同,在REPL和break循环中交互式调试较少......
答案 1 :(得分:1)
我不是Lisp专家,但我已经看到大量代码使用*standard-output*
的隐含值。来自lisp社区的论点是,这种方法使得代码更容易在REPL中运行/测试(我来自C / Java背景,所以任何闻到全局变量的东西都让人感到不安,但它是lisp方式)。
关于并发性,CL中的每个线程都有*standard-output*
的不同副本,因此您的线程将是安全的,但您需要正确配置它们。您可以在lisp cookbook - threads部分阅读更多相关信息。
答案 2 :(得分:1)
我只是想补充一点,你可以在Common Lisp中做的一件事是结合两种做法:
(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."))
由于*standard-output*
是参数的名称,因此使用stream参数调用explicit
会自动将动态变量*standard-output*
重新绑定到该参数的值。
答案 3 :(得分:1)
我在Common Lisp中看到的唯一统计相关模式,除了显式传递流之外,是一个可选的流参数,默认为*standard-input*
或*standard-output*
,具体取决于函数所需的方向。
Common Lisp中的隐式案例都涉及未指定的输入/输出,例如:
y-or-n-p
/ yes-or-no-p
使用*query-io*
apropos
,disassemble
和room
使用*standard-output*
describe
可以使用*standard-output*
或*terminal-io*
trace
/ untrace
和time
使用*trace-output*
dribble
可能绑定*standard-input*
和/或*standard-output*
step
和inspect
可以做任何他们想做的事情,从无到有,到标准输入和标准输出命令循环,到显示图形工具窗口
所以,我相信您可能看到的所有其他案例都来自图书馆。我的建议是不要遵循任何隐含的模式。但是,一个很好的例外是HTML生成器,它绑定一些变量,例如*html-stream*
以便后面的宏可以引用该变量而不会出现混乱。想象一下,如果你必须告诉每个宏上的流(不是一个真实的例子):
(html
(head (title "Foo"))
(body (p "This is just a simple example.")
(p "Even so, try to imagine this without an implicit variable.")))
有关实例,请查看(至少)CL-WHO (with-html-output)和AllegroServe's HTML generator。
所以,这里的优势纯粹是合成的。
使用动态绑定从来没有性能原因。可能存在堆栈空间原因,以避免将流作为参数传递,但这是一个非常弱的原因,任何现有的递归都会稍微吹一点。