design-patterns - 口齿不清 : dynamic scope vs explicit parameter passing

标签 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 或其他任何东西,除了生成打印表示之外,预计不会做任何不同的事情。

除了风格之外,是否存在任何重要的差异,例如关于性能、并发性或其他方面?

最佳答案

其实你可以直接绑定(bind)*standard-output*:

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

没有真正简单的答案。我的建议:

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

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*))
  ...)

还要注意这一点:

现在假设 (implicit) 有一个错误,我们得到一个 break loop,一个调试 repl。在这个break loopstandard-output的值是多少?

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
"

以上是您在 LispWorks 或 SBCL 中看到的内容。这里你可以访问真实程序的绑定(bind),但是在调试过程中使用输出函数会对这个流产生影响。

在其他实现中,*standard-output* 将被反弹到实际的终端 io - 例如在 Clozure CL 和 CLISP 中。

如果您的程序没有重新绑定(bind)*standard-output*,那么在这些情况下困惑就会减少。如果我编写代码,我经常会考虑在 REPL 环境中什么会更有用 - 这与语言不同,REPL 和中断循环的交互式调试较少......

关于design-patterns - 口齿不清 : dynamic scope vs explicit parameter passing,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30555526/

相关文章:

java - 如何格式化类中的方法

parallel-processing - 键的替代哈希表相等性测试

c++ - 如何设计类方法只能被调用一次

c++ - 在 C++ 中使用 "super"

c++ - 通过工厂方法的对象实例化没有给出期望的结果

coding-style - 完全限定的命名与 using 指令只是一个意见问题吗?

lisp - 在 Lisp 中如何调用函数列表中的函数?

lisp - 测试一个类是否是普通 lisp 中另一个类的子类

android - 如何使用 RxJava 实现 NullObject 模式

node.js - 将对象的更新从客户端发送到 nodejs