当我大致了解替换模型(具有参考透明性(RT))时,可以将函数分解为最简单的部分。如果表达式是RT,则可以分解表达式并始终获得相同的结果。
是的,直觉是正确的。以下是一些更精确的指示:
就像您说的那样,任何RT表达式都应具有single
“结果”。也就是说,给定factorial(5)
程序中的表达式,它应始终产生相同的“结果”。因此,如果程序中存在某个确定factorial(5)
值并且产生120,则无论扩展/计算哪个“步骤顺序”(与时间无关),它都应始终产生120 。
示例:factorial
功能。
def factorial(n):
if n == 1:
return 1
return n * factorial(n - 1)
此说明有一些注意事项。
首先,请记住,对于相同的RT表达,不同的评估模型(请参见适用顺序与正常顺序)可能会产生不同的“结果”。
def first(y, z):
return y
def second(x):
return second(x)
first(2, second(3)) # result depends on eval. model
在上面的代码中,first
并且second
是引用透明的,但是,如果以正常顺序和应用顺序(在后者下,该表达不停止)进行评估,则最后的表达式将产生不同的“结果”。
....这导致在引号中使用“结果”。由于不需要暂停表达式,因此它可能不会产生值。因此,使用“结果”有点模糊。可以说RT表达式computations
在评估模型下总是产生相同的结果。
第三,可能需要看到两个foo(50)
以不同的表达式出现在程序中的不同位置,每个表达式产生的结果可能彼此不同。例如,如果语言允许动态范围,则两个表达式尽管在词法上相同,但都是不同的。在perl中:
sub foo {
my $x = shift;
return $x + $y; # y is dynamic scope var
}
sub a {
local $y = 10;
return &foo(50); # expanded to 60
}
sub b {
local $y = 20;
return &foo(50); # expanded to 70
}
动态范围会产生误导,因为它使人们容易想到它x
是的唯一输入foo
,而实际上它是x
和y
。看到差异的一种方法是将程序转换为没有动态范围的等效程序-即显式传递参数,因此foo(x)
,我们定义而不是定义,foo(x, y)
并y
在调用程序中显式传递。
关键是,我们始终处于一种function
思维定势:给表达式一个特定的输入,就给我们一个相应的“结果”。如果我们提供相同的输入,则我们应该始终期待相同的“结果”。
现在,下面的代码呢?
def foo():
global y
y = y + 1
return y
y = 10
foo() # yields 11
foo() # yields 12
该foo
过程中断RT,因为存在重新定义。也就是说,我们y
在一点上进行了定义,然后再重新定义了同一点 y
。在上面的perl示例中,y
尽管s共享相同的字母名称“ y”,但它们是不同的绑定。这里的y
s实际上是相同的。这就是为什么我们说(重新分配)是一个元操作:实际上,您正在更改程序的定义。
大致来说,人们通常将差异描述如下:在无副作用的环境中,您有的映射input -> output
。在“命令式”设置中,您input -> ouput
所处的环境state
会随时间而变化。
现在,不仅state
要用表达式替换其对应的值,还必须在需要它的每个操作上对进行转换(当然,表达式可以参考相同的表达式state
来执行计算)。
因此,如果在无副作用程序中,计算表达式所需的全部是其单个输入,则在命令式程序中,对于每个计算步骤,我们都需要知道输入和整个状态。推理是第一个遭受重大打击的人(现在,要调试有问题的过程,您需要输入和核心转储)。某些技巧不实用,例如记忆。而且,并发和并行性也变得更具挑战性。
RT
禁止你使用substitution model.
的一个重要问题不能够使用的substitution model
是用它来推理程序的权力?