dav*_*ugh 0 global-variables dynamic common-lisp
我希望这不会打败您,但我想对编写引用透明代码的另一种可能策略提出意见。(先前有关引用透明性的讨论在“ 使用闭包而不是全局变量”中)。再次,目标是消除大多数全局变量,但保留其便利性,而无需在代码中注入容易产生错误的引用或潜在的非功能性行为(即,参照不透明,副作用和不可重复的评估)。
建议使用局部特殊变量建立初始绑定,然后可以将其动态传递给最终使用它们的后续嵌套函数。像全局变量一样,其预期的优点是不需要将局部特殊变量作为参数传递给所有中间函数(其功能与局部特殊变量无关)。但是,为了保持引用透明性,它们将作为参数传递给最终使用者函数。
我想知道的是,是否在浮动很多动态变量时容易产生编程错误。我似乎不太容易出错,因为任何先前绑定的变量的本地重新绑定在释放后都不会影响原始绑定:
(defun main ()
(let ((x 0))
(declare (special x))
(fum)))
(defun fum ()
(let ((x 1)) ;inadvertant? use of x
(setf x 2))
(foo))
(defun foo ()
(declare (special x))
(bar x))
(defun bar (arg) ;final consumer of x
arg)
(main) => 0
Run Code Online (Sandbox Code Playgroud)
这种策略有问题吗?
现在,您的函数正在引用不能保证定义的变量。尝试(foo)在repl 上执行将引发未绑定变量错误。不仅存在参照不透明,而且现在引发参照上下文错误!
您在这里拥有的是全局绑定的例程,这些例程只能在(declare (special x))已提示的本地上下文中执行。您也可以将这些函数放在a中,labels以免被意外使用,尽管此时您可以选择关闭函数中的变量还是关闭函数中的函数:
(defun main ()
(labels ((fum ()
(let ((x 1));Inadvertent use of x?
(setf x 2))
(foo))
(foo ()
(declare (special x))
(bar x))
(bar (arg) arg)) ;Final consumer of x.
(let ((x 0))
(declare (special x))
(fum))))
Run Code Online (Sandbox Code Playgroud)
哇,那是丑陋的代码!
卷积后,我们可以使x词法!现在我们可以实现圣杯,参照透明!
卷积
(defun main ()
(let ((x 0))
(labels ((fum ()
(let ((x 1))
(setf x 2))
(foo))
(foo () (bar x))
(bar (arg) arg));Final consumer of x.
(fum))))
Run Code Online (Sandbox Code Playgroud)
这段代码好得多,也很简单。从本质上讲,这是您对其他问题的代码,但是函数绑定已本地化。这至少比使用爆炸性全局命名更好。内部let不会执行任何操作,与以前一样。尽管现在不那么令人费解了。
CL-USER> (main) ;=> 0
Run Code Online (Sandbox Code Playgroud)
您的测试用例(main) ;=> 0在两个方面都是相同的。原理是只按顺序封装变量,而不用动态special声明。现在,如建议的那样,通过仅在单个环境变量中进行功能传递就可以进一步减少代码。
(defun convoluted-zero ()
(labels ((fum (x)
(let ((x 1))
(setf x 2))
(foo x))
(foo (x) (bar x))
(bar (arg) arg)).
(fum 0)))
Run Code Online (Sandbox Code Playgroud)
CL-USER> (let ((x (convoluted-zero)))
(list x (convoluted-zero)))
;=> 0
Run Code Online (Sandbox Code Playgroud)
?使用特殊变量对您的代码进行QED违反了抽象。
如果您真的想钻进兔子洞,可以阅读Pandoric宏中Doug Hoyte的Let Over Lambda的第6章中的部分,您可以执行以下操作:
(use-package :let-over-lambda)
(let ((c 0))
(setf (symbol-function 'ludicrous+)
(plambda () (c) (incf c)))
(setf (symbol-function 'ludicrous-)
(plambda () (c)(decf c))))
Run Code Online (Sandbox Code Playgroud)
然后,您可以使用它pandoric-get来获取c,而无需增加它或在该上下文中定义任何访问器函数,这绝对是傻瓜。使用lisp软件包,您可以摆脱软件包本地的“全局”变量。例如,我可以在elisp中看到一个没有内置软件包的应用程序。