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

Design patterns lisp:动态范围与显式参数传递,design-patterns,coding-style,common-lisp,anti-patterns,dynamic-scope,Design Patterns,Coding Style,Common Lisp,Anti Patterns,Dynamic Scope,我在(通用)lisp中看到两种不同的“输出”函数模式: 像implicit中那样使用动态范围被认为是不好的做法,还是这是一种普遍接受的动态范围的使用?请注意,我假设这是为DSL构建复杂的输出,如HTML、SVG、Latex等,并且除了生成打印的表示之外,不需要做任何不同的事情 除了风格之外,还有什么重要的区别吗,例如在性能、并发性或其他方面?我不是Lisp专家,但我见过很多代码使用隐式值作为*标准输出*。lisp社区的观点是,这种方法使代码更容易在REPL中运行/测试(我来自C/Java背景,因

我在(通用)lisp中看到两种不同的“输出”函数模式:

implicit
中那样使用动态范围被认为是不好的做法,还是这是一种普遍接受的动态范围的使用?请注意,我假设这是为DSL构建复杂的输出,如HTML、SVG、Latex等,并且除了生成打印的表示之外,不需要做任何不同的事情


除了风格之外,还有什么重要的区别吗,例如在性能、并发性或其他方面?

我不是Lisp专家,但我见过很多代码使用隐式值作为
*标准输出*
。lisp社区的观点是,这种方法使代码更容易在REPL中运行/测试(我来自C/Java背景,因此任何带有全局变量味道的东西都会让人感到不安,但这是lisp方法)


关于并发性,CL中的每个线程都有一个不同的
*标准输出*
,因此线程是安全的,但您需要正确配置它们。您可以在本节中进一步了解这一点。

实际上,您可以直接绑定
*标准输出*

(defun test-im-vs-ex-plicit ()
  (values
   (with-output-to-string (*standard-output*)   ; here
     (implicit))
   (with-output-to-string (stream)
     (explicit stream))))
没有真正简单的答案。我的建议:

使用流变量,这使调试更容易。它们出现在参数列表中,在回溯中更容易发现。否则,您需要在回溯中看到流变量的动态重新绑定

a) 没什么可以通过的吗

(defun print-me (&optional (stream *standard-output*))
  ...)
b) 一个或多个固定参数:

(defun print-me-and-you (me you &optional (stream *standard-output*))
  ...)
c) 一个或多个固定参数和多个可选参数:

(defun print-me (me
                 &key
                 (style  *standard-style*)
                 (font   *standard-font*)
                 (stream *standard-output*))
  ...)
请注意:

现在假设
(隐式)
有一个错误,我们得到一个中断循环,一个调试应答。这个中断循环中的标准输出值是多少

CL-USER 4>(除雾测试()
(flet((隐式()
(写行“foo”)
(cerror“继续”“休息一下”)
(写行“bar”))
(输出为字符串(流)
(let((*标准输出*流))
(隐含的((())))
试验
CL-USER 5>(编译“测试”)
试验
无
无
CL-USER 6>(测试)
错误:只是休息一下
1(继续)继续
2(中止)返回到级别0。
3返回顶部循环级别0。
键入:b表示回溯,或键入:c表示继续。
键入:错误报告模板的错误表单“”,或:?其他选择。
CL-USER 7:1>*标准输出*
#
CL-USER 8:1>(写行“baz”)
“巴兹”
CL-USER 9:1>:C1
“福
巴兹
酒吧
"
以上是您在LispWorks或SBCL中看到的内容。在这里,您可以访问实际程序的绑定,但在调试期间使用输出函数将对该流产生影响

在其他实现中,
*标准输出*
将恢复到实际的终端io-例如在Clozure CL和CLISP中


如果您的程序没有重新绑定
*标准输出*
,那么在这些情况下就不会有太多混淆。如果我写代码,我经常会想,在REPL环境中什么更有用?这与语言不同,在REPL环境中,REPL和中断循环上的交互调试较少…

我只想补充一点,在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."))

由于
*标准输出*
是参数的名称,因此使用流参数调用
显式
会自动将动态变量
*标准输出*
重新绑定到该参数的值。

除了显式传递流之外,我在Common Lisp本身看到的唯一统计相关模式,是一个可选的流参数,默认为
*标准输入*
*标准输出*
,具体取决于函数所需的方向

Common Lisp中的隐式用例都处理未指定的输入/输出,例如:

  • 使用
    *查询io*

  • apropos
    反汇编
    房间
    使用
    *标准输出*

  • 描述可以使用
    *标准输出*
    *终端io*

  • 跟踪
    /
    取消跟踪
    时间
    使用
    *跟踪输出*

  • 运球
    可以绑定
    *标准输入*
    和/或
    *标准输出*

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

所以,我相信你可能看到的所有其他案例都来自图书馆。我的建议是不要遵循任何隐含的模式。但是,HTML生成器中有一个很好的例外,它绑定了一些变量,例如
*HTML stream*
,这样后面的宏就可以引用该变量而不会造成混乱。想象一下,如果必须告诉每个宏上的流(不是真实的示例):

对于真实的例子,请查看(至少)和

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


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

在REPL中进行更简单的测试确实是一个参数。我也有C/Java背景,这就是为什么我担心隐式使用是不好的做法。动态范围感觉有点像类固醇上的全局变量。感谢您提供有关线程的指针!是的,这是参数列表中特殊变量的结果。我不记得我在实际代码中看到过,但是……对于给定的数据块,如果它是“上下文”,并且每次调用都不会有太大变化,那么它可能是一个动态变量。如果它是“特定的”,并且可能会随着每次通话而改变
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
"
(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."))
(html
  (head (title "Foo"))
  (body (p "This is just a simple example.")
        (p "Even so, try to imagine this without an implicit variable.")))