问题描述
我希望这不会打败您,但是我想就编写参照透明代码的另一种可能策略提出意见。 (以前有关引用透明性的讨论位于)。再次,目标是消除大多数全局变量,但保留它们的方便性,而不会在代码中注入容易出错的引用或潜在的非功能性行为(即,引用不透明,副作用和不可重复的评估)。
I hope this isn't beating a dead horse, but I'd like an opinion about another possible strategy for writing referentially transparent code. (The previous discussion about referential transparency is at Using a Closure instead of a Global Variable). Again, the objective is to eliminate most global variables, but retain their convenience, without injecting bug-prone references or potentially non-functional behavior (ie, referential opaqueness, side-effects, and non-repeatable evaluations) into the code.
建议使用局部特殊变量建立初始绑定,然后可以将其动态传递给最终使用它们的后续嵌套函数。像全局变量一样,其预期的优点是不需要将局部特殊变量作为参数传递给所有中间函数(其功能与局部特殊变量无关)。但是,为了保持引用的透明性,它们将作为参数传递给最终的使用者函数。
The proposal is to use local special variables to establish initial bindings, which can then be passed dynamically to the subsequent nested functions that eventually use them. The intended advantage, like globals, is that the local special variables do not need to be passed as arguments through all the intermediate functions (whose functionality has nothing to do with the local special variables). However to maintain referential transparency, they would be passed as arguments to the final consumer functions.
我想知道的是,是否容易产生很多动态变量编程错误。我似乎不太容易出错,因为任何先前绑定的变量的本地重新绑定在释放后都不会影响原始绑定:
What I'm wondering about is whether floating a lot of dynamic variables around is prone to programming bugs. It doesn't seem particularly error prone to me, since any local rebinding of a previously bound variable should not affect the original binding, once it is released:
(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
这种策略是否存在问题?
Are there problems with this stragegy?
推荐答案
现在,您的函数正在引用不能保证定义的变量。尝试在repl上执行(foo)
会引发未绑定的变量错误。不仅存在参照不透明,而且现在引发参照上下文错误!
Now your functions are referencing a variable that is not guaranteed to be defined. Trying to execute (foo)
at the repl will throw an unbound variable error. Not only is there referential opacity, but now referential context error throwing!
这里具有的是全局绑定例程,只能执行在暗示了(declare(special x))
的本地上下文中。您也可以将这些函数放在标签
中,以免被意外使用,尽管此时您可以选择关闭函数中的变量还是关闭函数在函数中
What you have here are globally bound routines, which can only be executed in the local context where (declare (special x))
has been hinted. You may as well put those functions in a labels
so they don't get accidentally used, though at that point you are choosing between closing the variables in functions, or closing the functions in a function:
(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))))
哇,有些丑陋的代码!
卷积后,我们可以使 x
成为词法!现在,我们可以实现圣杯,参照透明!
After a convolution we can make x
lexical! Now we can achieve the holy grail, referential transparency!
卷积
(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))))
此代码好得多,并且lispy。它实际上是,但是函数绑定已本地化。这至少比使用爆炸性全局命名更好。内部let不做任何事情,和以前一样。
This code is much nicer, and lispy. It is essentially your code to the other question, but the functions bindings are localized. This is at least better than using explosive global naming. The inner let does nothing, same as before. Though now it is less convoluted.
CL-USER> (main) ;=> 0
您的测试用例是相同的(main); =>两者都为0
。原理是只按顺序封装变量,而不用动态的 special
声明。现在,我们只需在单个环境变量。
Your test case is the same (main) ;=> 0
in both. The principle is to just encapsulate your variables lexially instead of with dynamic special
declarations. Now we can reduce the code even more by just passing things functionally in a single environment variable, as suggested.
(defun convoluted-zero ()
(labels ((fum (x)
(let ((x 1))
(setf x 2))
(foo x))
(foo (x) (bar x))
(bar (arg) arg)).
(fum 0)))
CL-USER> (let ((x (convoluted-zero)))
(list x (convoluted-zero)))
;=> 0
□ QED 您的带有特殊变量的代码违反了抽象。
□ QED your code with the special variables violates abstraction.
如果您真的想钻进兔子洞,可以阅读有关道格拉斯宏的道格·霍伊特(Doug Hoyte)让Lambda 的第六章的部分,您可以在其中执行以下操作:
If you really want to go down the rabbit hole, you can read the section of chapter 6 of Doug Hoyte's Let Over Lambda on pandoric macros, where you can do something like this:
(use-package :let-over-lambda)
(let ((c 0))
(setf (symbol-function 'ludicrous+)
(plambda () (c) (incf c)))
(setf (symbol-function 'ludicrous-)
(plambda () (c)(decf c))))
然后可以使用 pandoric-get
在不增加c的情况下获取c或在该上下文中定义任何访问器函数,这绝对是傻瓜。使用lisp软件包,您可以摆脱软件包本地的全局变量。例如,我可以在elisp中看到一个应用程序,其中没有内置软件包。
You can then use pandoric-get
to get c without incrementing it or defining any accessor function in that context, which is absolute bonkers. With lisp packages you can get away with a package-local "global" variable. I could see an application for this in elisp, for example, which has no packages built in.
这篇关于使用传递的局部特殊变量作为最终参数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!