这个问题是Using Local Special Variables上有关如何最好地避免全局变量的注释的继续。据我了解,全局变量存在问题,主要是因为它们有可能干扰参照透明性。如果表达式使用其调用上下文之外的信息来更改全局值(例如,全局变量本身的先前值或任何其他外部值),则会违反透明度。在这些情况下,评估表达式在不同时间可能有不同的结果,无论是返回的值还是副作用。 (但是,似乎并不是所有的全局更新都是有问题的,因为某些更新可能不依赖于任何外部信息,例如,将全局计数器重置为0)。深度嵌入计数器的常规全局方法可能类似于:
* (defparameter *x* 0)
*X*
* (defun foo ()
(incf *x*))
FOO
* (defun bar ()
(foo))
BAR
* (bar)
1
* *x*
1
这似乎违反了参照透明性,因为(incf *x*)
依赖于*x*
的外部(全局)值来进行工作。以下是通过消除全局变量来维持功能和引用透明性的尝试,但是我不相信它确实可以做到:
* (let ((x 0))
(defun inc-x () (incf x))
(defun reset-x () (setf x 0))
(defun get-x () x))
GET-X
* (defun bar ()
(inc-x))
BAR
* (defun foo ()
(bar))
FOO
* (get-x)
0
* (foo)
1
* (get-x)
1
现在全局变量已消失,但表达式(inc-x)
似乎仍具有(潜在)副作用,并且每次调用它都会返回不同(但未使用)的值。这是否确认对相关变量使用闭包不能解决透明性问题?
答案 0 :(得分:8)
全局变量之所以成问题,主要是因为它们有可能干扰参照透明性
如果要创建全局配置值,则Common Lisp中的全局变量就可以了。
通常最好打包一堆配置状态,然后将其放入对象中可能会更好。
没有普遍要求程序是参照透明。
按照软件工程原理指导软件设计很有用,但是容易调试和维护通常比严格原则更重要。
(let ((x 0))
(defun inc-x () (incf x))
(defun reset-x () (setf x 0))
(defun get-x () x))
实际上是上面的意思
答案 1 :(得分:5)
引用透明性意味着,如果将某个变量x
绑定到表达式e
,则可以用x
替换所有出现的e
,而不会更改结果。例如:
(let ((e (* pi 2)))
(list (cos e) (sin e)))
上面可以这样写:
(list (cos (* pi 2))
(sin (* pi 2)))
对于等效性的一些有用定义,结果值等于第一个(在equalp
,但是您可以选择另一个)。与此相反:
(let ((e (random))
(list e e))
在上面,每次random
的调用都会给出不同的结果(统计上),因此,如果您多次重复使用相同的结果或在每次调用之后生成新的结果,则行为是不同的。
特殊变量就像函数的其他参数一样,它们可以通过绑定到不同的值来简单地影响结果。考虑用于构建路径名的*default-pathname-defaults*
。
实际上,对于给定的变量绑定,每次对(merge-pathnames "foo")
的调用都会返回相同的结果。仅当您在不同的动态上下文中使用相同的表达式时,结果才会更改,这与调用具有不同参数的函数没有什么不同。
主要困难是隐藏了特殊变量,即您可能不知道它会影响某些表达式,这就是为什么需要对它们进行记录和数量限制的原因。
打破引用透明性的是副作用,无论您使用的是词法变量还是特殊变量。在这两种情况下,位置都会作为函数执行的一部分进行修改,这意味着您需要考虑何时和调用频率。
如果您进一步解释代码的组织方式,则可能会有更好的建议。您说过,由于 prototyping ,您有许多特殊变量,但是在重构中,您想做的似乎就像您想保持原型代码基本不变。也许有一种方法可以用一种很好的模块化方式打包东西,但是如果您不了解为什么需要很多特殊变量等信息,我们将无济于事。
答案 2 :(得分:1)
该代码不是参照透明的。不过,它是对special
变量的改进。
如果您删除了reset-x
,则放置的代码将是功能nonce。
我对your previous question的回答具有关于special
变量的一般准则。对于您的特定情况,也许值得吗?我可以看到使用特殊变量作为随机数的情况,例如,将它们传递到周围可能很愚蠢。
Common Lisp具有许多用于处理全局信息的功能,因此很少需要具有许多全局变量。您可以定义一个*env*
列表来存储您的值,或将它们放在哈希表中,或将它们放入符号栈中,或将它们打包在一个闭包中以进行传递,或执行其他操作,或使用CLOS。
答案 3 :(得分:1)
第二个示例的副作用在哪里? x
内部的let
无法从外部访问。
这是另一个闭包示例,其中包含顶级功能,并且内部明确包含一个计数器。
(defun repeater (n)
(let ((counter -1))
(lambda ()
(if (< counter n)
(incf counter)
(setf counter 0)))))
(defparameter *my-repeater* (repeater 3))
;; *MY-REPEATER*
(funcall *my-repeater*)
0
(funcall *my-repeater*)
1
https://lispcookbook.github.io/cl-cookbook/functions.html#closures