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

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

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

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


当前回答

我觉得这里有点误会。纯函数式程序有状态。不同之处在于该状态是如何建模的。在纯函数式编程中,状态是由接受某个状态并返回下一个状态的函数操作的。然后通过将状态传递给纯函数序列来实现状态排序。

甚至全局可变状态也可以这样建模。例如,在Haskell中,程序是一个从World到World的函数。也就是说,你传入整个宇宙,程序返回一个新的宇宙。但是,在实践中,您只需要传入您的程序实际感兴趣的部分。程序实际上返回一系列动作,作为程序运行的操作环境的指令。

您希望从命令式编程的角度对此进行解释。好的,让我们看一些用函数式语言编写的非常简单的命令式编程。

考虑下面的代码:

int x = 1;
int y = x + 1;
x = x + y;
return x;

相当标准的命令式代码。没有做什么有趣的事情,但这是可以用来说明的。我想你们会同意这里涉及到国家。变量x的值随时间变化。现在,让我们稍微改变一下符号,发明一个新的语法:

let x = 1 in
let y = x + 1 in
let z = x + y in z 

加上括号使它的意思更清楚:

let x = 1 in (let y = x + 1 in (let z = x + y in (z)))

可以看到,状态是由一系列纯表达式建模的,这些纯表达式绑定了下面表达式的自由变量。

您会发现这个模式可以建模任何类型的状态,甚至IO。

其他回答

你不可能有一种有用的纯函数式语言。总会有一定程度的可变性需要处理,IO就是一个例子。

将函数式语言视为您使用的另一种工具。它对某些事情有好处,但对其他事情没有好处。你给出的游戏例子可能不是使用函数语言的最佳方式,至少屏幕会有一个可变的状态,你不能用FP做任何事情。使用FP思考问题的方式和解决问题的类型将不同于使用命令式编程所习惯的方式。

这很简单。在函数式编程中,你可以使用任意多的变量……但前提是它们是局部变量(包含在函数中)。因此,只需将代码包装在函数中,在这些函数之间来回传递值(作为传递的参数和返回值)……这就是它的全部!

这里有一个例子:

function ReadDataFromKeyboard() {
    $input_values = $_POST[];
    return $input_values;
}
function ProcessInformation($input_values) {
    if ($input_values['a'] > 10)
        return ($input_values['a'] + $input_values['b'] + 3);
    else if ($input_values['a'] > 5)
        return ($input_values['b'] * 3);
    else
        return ($input_values['b'] - $input_values['a'] - 7);
}
function DisplayToPage($data) {
    print "Based your input, the answer is: ";
    print $data;
    print "\n";
}

/* begin: */
DisplayToPage (
    ProcessInformation (
        GetDataFromKeyboard()
    )
);

使用一些创造性和模式匹配,无状态游戏被创造出来:

迷宫游戏 迷宫游戏2 CSSPlay:井字 纯CSS一字棋 CSSPlay:乒乓球 CSSPlay:乒乓球 CSSPlay:警察与强盗 CSSPlay: Whack-a-Rat CSS3 Pong:与CSS有关的疯狂事情

以及滚动演示:

随机英雄 动画模拟SVG时钟 动画SVG摆 动画SVG赛车手 CSS3:造雪

和可视化:

XSLT 曼德尔布洛特

让我们来回答更普遍的问题:

没有状态,你怎么做有用的事情?

你不。

寻找传统语言的替代品 我们必须首先认识到,一种制度不可能成为历史 敏感(允许执行一个程序来影响 一个后续的行为),除非系统已经 某种状态(第一个程序可以改变这种状态) 而第二个可以访问)。因此,历史敏感 计算系统的模型必须具有状态转换 语义学,至少在这个弱意义上。 约翰·巴克斯。

(由我强调)

重要的是巴克斯随后的观察:

但这不是 意味着每个计算都必须严重依赖于 复杂状态[…]

像Haskell或Clean这样的函数式语言允许您轻松地将这种观察付诸实践:大多数定义都是普通的函数,就像您在数学教育中看到的那样。这就留下了一小群“杂七杂八的人”来处理所有恼人的外部状态,例如:

与用户互动, 与远程服务通信, 处理模拟使用随机抽样, 打印出一个SVG文件(例如海报), 定期备份,

...两种语言都使用类型来区分普通和混杂。


有时,如果您试图实现的算法使用私有、局部可变状态实现,则效果最好。在这种情况下,你可以使用Haskell扩展来做到这一点,而不会让整个程序变得“内部杂乱”——详情请参阅John Launchbury和Simon Peyton Jones编写的State In Haskell。

Functional programming avoids state and emphasizes functionality. There's never any such thing as no state, though the state might actually be something that's immutable or baked into the architecture of what you're working with. Consider the difference between a static web server that just loads up files off the filesystem versus a program that implements a Rubik's cube. The former is going to be implemented in terms of functions designed to turn a request into a file path request into a response from the contents of that file. Virtually no state is needed beyond a tiny bit of configuration (the filesystem 'state' is really outside the scope of the program. The program works the same way regardless of what state the files are in). In the latter though, you need to model the cube and your program implementation of how operations on that cube change its state.