lisp:动态范围与显式参数传递

时间:2015-05-31 08:49:06

标签: design-patterns coding-style common-lisp anti-patterns dynamic-scope

我在(常见)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等等,除了生成打印表示外,预计不会做任何不同的事情。

除了风格之外,还有任何重要的差异,例如:在性能,并发性等方面?

4 个答案:

答案 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*

  • aproposdisassembleroom使用*standard-output*

  • describe可以使用*standard-output**terminal-io*

  • trace / untracetime使用*trace-output*

  • dribble可能绑定*standard-input*和/或*standard-output*

  • stepinspect可以做任何他们想做的事情,从无到有,到标准输入和标准输出命令循环,到显示图形工具窗口

所以,我相信您可能看到的所有其他案例都来自图书馆。我的建议是不要遵循任何隐含的模式。但是,一个很好的例外是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

所以,这里的优势纯粹是合成的。

使用动态绑定从来没有性能原因。可能存在堆栈空间原因,以避免将流作为参数传递,但这是一个非常弱的原因,任何现有的递归都会稍微吹一点。