我开始围绕Haskell进行一些激动人心的实验。而且我似乎无法理解一件事(以前的#34;总统和#34;经验谈判可能)。 最近,我渴望实现整数除法功能,就好像没有乘法/除法运算一样。一个非常有趣的脑筋急转弯导致了很大的混乱。
divide x y =
if x < y then 0
else 1 + divide (x - y) y
我编译了它并且它工作(!)。那令人兴奋。但是,有人告诉我,我确信变量在Haskell中是不可变的。为什么每个递归步骤变量x保持上一步的值?或者我光荣的编译器对我说谎?它为什么会起作用?
答案 0 :(得分:4)
您的x
在一次函数调用期间(即创建后)不会更改 - 这正是immutable的含义。在多个(递归)调用期间,更改是x
的值。在单个堆栈帧(函数调用)中,x
的值是常量。
执行代码的示例,简单案例
call divide 8 3 -- (x = 8, y = 3), stack: divide 8 3
step 1: x < y ? NO
step 2: 1 + divide 5 3
call: divide 5 3 -- (x = 5, y = 3), stack: divide 8 3, divide 5 3
step 1: x < y ? NO
step 2: 1 + divide 2 3
call divide 2 3 -- (x = 2, y = 3), stack: divide 8 3, divide 5 3, divide 2 3
step 1: x < y ? YES
return: 0 -- unwinding bottom call
return 1 + 0 -- stack: divide 8 3, divide 5 3, unwinding middle call
return 1 + 1 + 0 -- stack: divide 8 3
我知道上面的表示法无论如何都没有形式化,但我希望有助于理解递归是什么,x
在不同的调用中可能有不同的值,因为它只是整个调用的不同实例,因此也是x
的不同实例。
答案 1 :(得分:4)
x
实际上不是变量,而是参数,并且与命令式语言中的参数不同。
使用显式返回语句可能看起来更明显吗?
-- for illustrative purposes only, doesn't actually work
divide x y =
if x < y
then return 0
else return 1 + divide (x - y) y
你不会改变x
,只是堆叠几个函数调用来计算你想要的结果以及它们返回的值。
这里的Python功能相同:
def divide(x, y):
if x < y:
return 0
else:
return 1 + divide(x - y, y)
看起来很熟悉吧?您可以将其转换为允许递归的任何语言,并且它们都不会要求您改变变量。
除此之外,是的,你的编译器对你撒谎。因为您不允许直接改变值,编译器可以根据您的代码做出许多额外的假设,这有助于将其转换为高效的机器代码,并且在该级别上,没有逃避可变性。主要好处是编译器不太可能引入与可变性相关的错误。