最近我读了很多关于函数式编程的东西,大部分我都能理解,但有一件事我就是搞不懂,那就是无状态编码。在我看来,通过删除可变状态来简化编程就像通过删除仪表盘来“简化”一辆汽车:最终产品可能更简单,但希望它能与最终用户交互。

几乎我能想到的每个用户应用程序都将状态作为核心概念。如果你写了一个文档(或一个SO post),状态会随着每一个新的输入而改变。或者如果你玩电子游戏,会有大量的状态变量,从所有角色的位置开始,这些角色往往会不断移动。如果不跟踪不断变化的值,您怎么可能做任何有用的事情呢?

每次我发现一些讨论这个问题的东西,它都是用真正的技术函数语言写的,假设我没有浓厚的FP背景。有谁知道如何向那些对命令式编码有很好的、扎实的理解,但在函数方面完全是n00b的人解释这一点吗?

编辑:到目前为止,一堆回复似乎试图让我相信不可变值的优点。我懂你的意思。这很有道理。我不明白的是,在没有可变变量的情况下,如何跟踪必须不断变化的值。


当前回答

For highly interactive applications such as games, Functional Reactive Programming is your friend: if you can formulate the properties of your game's world as time-varying values (and/or event streams), you are ready! These formulae will be sometimes even more natural and intent-revealing than mutating a state, e.g. for a moving ball, you can directly use the well-known law x = v * t. And what's better, the game's rules written such way compose better than object-oriented abstractions. For example, in this case, the ball's speed can be also a time-varying value, which depends on the event stream consisting of the ball's collisions. For more concrete design considerations, see Making Games in Elm.

其他回答

通过使用大量的递归。

用f#(一种函数式语言)玩地字游戏。

注意,说函数式编程没有“状态”有点误导,可能是造成混淆的原因。它肯定没有“可变状态”,但它仍然可以有被操纵的值;它们只是不能就地更改(例如,您必须从旧值创建新值)。

这是一个严重的过度简化,但是想象一下你有一个OO语言,其中类上的所有属性只在构造函数中设置一次,所有方法都是静态函数。您仍然可以通过让方法获取包含计算所需的所有值的对象,然后返回带有结果的新对象(甚至可能是同一对象的新实例)来执行几乎任何计算。

将现有代码转换为这种范式可能“很难”,但这是因为它确实需要一种完全不同的思考代码的方式。但作为一个副作用,在大多数情况下,您可以免费获得大量并行机会。

附录:(关于如何跟踪需要更改的值的编辑) 当然,它们会被存储在一个不可变的数据结构中……

这不是一个建议的“解决方案”,但最简单的方法是,你可以将这些不可变的值存储到一个类似map(字典/哈希表)的结构中,以“变量名”为键。

显然,在实际解决方案中,您应该使用更明智的方法,但这确实表明,如果其他方法都不起作用,那么在最坏情况下,您可以使用这样一个贯穿调用树的映射来“模拟”可变状态。

这就是没有COMMON块的FORTRAN的工作方式:您将编写具有传入值和局部变量的方法。就是这样。

面向对象编程将我们的状态和行为结合在一起,但当我在1994年第一次从c++中接触到它时,它还是一个新思想。

天啊,当我还是机械工程师的时候,我是一个函数式程序员,而我却不知道!

下面是如何在没有可变状态的情况下编写代码:不是将变化状态放入可变变量中,而是将其放入函数的参数中。不写循环,而是写递归函数。比如这段命令式代码:

f_imperative(y) {
  local x;
  x := e;
  while p(x, y) do
    x := g(x, y)
  return h(x, y)
}

变成这样的函数代码(类似scheme的语法):

(define (f-functional y) 
  (letrec (
     (f-helper (lambda (x y)
                  (if (p x y) 
                     (f-helper (g x y) y)
                     (h x y)))))
     (f-helper e y)))

或者这个Haskellish代码

f_fun y = h x_final y
   where x_initial = e
         x_final   = loop x_initial
         loop x = if p x y then loop (g x y) else x

至于为什么函数式程序员喜欢这样做(你没有问),你的程序中无状态的部分越多,就有越多的方法可以在不中断的情况下将这些部分组合在一起。无状态范式的强大之处在于它本身不具有无状态性(或纯粹性),而在于它使您能够编写强大的、可重用的函数并将它们组合起来。

你可以在John Hughes的论文Why Functional Programming Matters中找到一个很好的教程,里面有很多例子。

For highly interactive applications such as games, Functional Reactive Programming is your friend: if you can formulate the properties of your game's world as time-varying values (and/or event streams), you are ready! These formulae will be sometimes even more natural and intent-revealing than mutating a state, e.g. for a moving ball, you can directly use the well-known law x = v * t. And what's better, the game's rules written such way compose better than object-oriented abstractions. For example, in this case, the ball's speed can be also a time-varying value, which depends on the event stream consisting of the ball's collisions. For more concrete design considerations, see Making Games in Elm.