TLDR:你可以在没有可变状态的情况下进行任何计算,但是当真正需要告诉计算机该做什么的时候,因为计算机只使用可变状态,你需要在某些时候改变一些东西。
有很多答案正确地说,没有可变状态就不能做任何有用的事情,我想用一些简单的(反)例子来支持这一点,以及一个普遍的直觉。
如果你看到任何一段被认为是“纯函数式”的代码,并且它是这样做的(不是真正的语言):
printUpToTen = map println [1..10]
这不是纯功能性的。有一个隐藏状态(stdout的状态)不仅被改变了,而且隐式地传递进来。看起来像这样的代码(同样不是真正的语言):
printUpToTen = map println stdout [1..10]
也是不纯的:即使显式地传入state (stdout),它仍然是隐式突变的。
现在直观地说:可变状态是必要的,因为影响我们计算机的核心构建块是可变状态,这个构建块是内存。我们不能强迫计算机做任何事情,而不以某种方式操纵内存,即使我们的计算模型确实可以计算任何东西,而没有“内存”的概念。
Think of something like an old GameBoy Advance: in order to display something to the screen, you must modify the memory (there are certain addresses that are read many times a second that determine whats being put on the screen). Your computational model (pure functional programming) may not need state to operate, you may even be able to implement you model using an imperative, state manipulation model (like assembly) that abstracts the state manipulation, but at the end of the day, somewhere in you code you have to modify those addresses in memory for the device to actually display anything.
这就是命令式模型具有天然优势的地方:因为它们总是在操作状态,所以您可以非常容易地将其转换为实际修改内存。下面是一个渲染循环的例子:
while (1) {
render(graphics_state);
}
如果你要展开循环,它看起来像这样:
render(graphics_state); // modified the memory
render(graphics_state); // modified the memory
render(graphics_state); // modified the memory
...
但在纯函数式语言中,你可能会得到这样的东西:
render_forever state = render_forever newState
where newState = render state
展开(准确地说是压平)可以像这样可视化:
render(render(render(render(...state) // when is the memory actually changing??
// or if you want to expand it the other direction
...der(render(render(render(render(render(state) // no mutation
正如你所看到的,我们在状态上一遍又一遍地调用一个函数,状态是不断变化的,但我们从不改变内存:我们立即将它传递给下一个函数调用。即使我们的实现实际上在底层修改了一些表示状态的东西(甚至在适当的位置!),它也不在正确的位置。在某些时候,我们需要暂停并修改内存中的正确地址,这涉及到突变。