“没有人可以两次穿越同一条河。”因为什么是河?我的意思是,我们喜欢这种物体的想法;就像这件事发生了变化。对?没有河。对?一个时间点那里有水。还有一个时间点,那里还有水。 — Rich Hickey,《我们还在那里吗》,引用赫拉克利特。
从用户的角度来看,功能程序可能看起来是有状态的。与上面的功能性ATM程序进行交互,并注意到该程序会记住以前的遭遇。一方面,这不足为奇。我们包括了一个命令层来记住以前的状态。我们没有将程序的状态分解为不同的对象(例如bankAccount和drawingAmount),而是创建了一个全局对象,即store。另一方面,专注于程序的“对象”部分则背离了面向对象的倾向。该程序的必要部分仅仅是用于促进基于另一个的异步计算的构造。甚至可以想象一种编程语言,其中在语言本身中内置了这种构造,从而从程序员的角度隐藏了任何命令式实现。实际上,存在可以编译为JavaScript的语言。 [1]换句话说,它是语法,而不是语义。程序的语义与递归的迭代函数的语义更好地吻合,在离散的步骤i处具有状态S-使用上一次运行的输出运行功能性ATM程序,以产生下一个运行的输入。
至少可以这样说,具有功能性,无状态且永恒的内核的程序可以维持状态令人惊讶。环顾房间,公共汽车,公园或任何您发现自己在阅读这句话的地方,您可能会发现“一系列不同的物体”,例如狗,人和树木,“其行为可能随时间而改变”。另一方面,环顾功能正常的ATM程序,找不到可识别的对象。但是,该程序似乎具有与房间中其他任何对象一样的状态。
但是,当我们对时间的概念的扩展超出了功能性程序的范围,从而涵盖了我们其余的物理现实时,表面上的“悖论”就消失了。
解决此矛盾的一种方法是认识到,用户的时间存在将状态强加于系统上。如果用户可以退出交互并从余额流而不是单个交易的角度进行思考,则系统将显示为无状态-SICP第3.5.5节
除了将世界视为其对象的总和,每个对象都随着时间的流逝反映其最新状态之外,我们还可以考虑离散的状态历史。正如我们将功能程序的状态解释为以不连续的步骤S(i)至S(i + 1)移动一样,我们可以将公园中的狗解释为以离散的步骤S(i)至S(i + 1)移动。 )。
考虑视频媒体。对于电影场景,我们可以赋予相同的面向对象语义。角色和无生命的物体会随着时间的流逝而发生转移,相互作用和进化。
例如,在播放以上视频时,我们可以得出结论:“一只猫在跳舞”。但是,视频是由静态帧组成的,这些静态帧以不连续的时间间隔按一定顺序缝合在一起。每个帧对应于某个时刻的视频状态,并且这些帧合在一起以时间计价的一系列离散状态。上面选择的媒体是有意的meta。该视频包括一幅由动画书[2]镜像的场景的电视动画,该场景显示了在离散时间间隔内串在一起的静态帧,而动画帧本身在“真实”生活中被一本动画书镜像了,示出了静态帧在同一时间串在一起。离散时间间隔。再次退后一步,注意上面在计算机上播放的gif媒体(如果您的浏览器支持html5,则为mp4)是由静态帧组成的,它们以不连续的时间间隔串在一起。
没有什么可以阻止我们再后退一步,并将您的计算机当前位于的真实世界解释为以离散时间间隔串在一起的静态帧。我们可以将普通的面向对象的语义归因于上述gif,从而得出“猫在跳舞”的结论。但是,我们也可以将功能语义归因于结论,即“猫在fᵢ框架上的头顶上方有手臂”。在现实世界的一个公园里,我们可以得出这样的结论:“一条狗在追一只松鼠。”但是,我们也可以得出这样的结论:“在帧f frame上,一只狗在松鼠后面的奔跑运动中。”在这两种情况下,我们都可以确定状态的时间序列,而不是随时间变化的对象。函数式编程范例可以一致地应用于世界和程序。
考虑到离散时间的模型,功能程序看起来有状态就不足为奇了。程序的用户可以被视为一系列状态,就像程序本身一样。一系列特定的用户状态,例如,
U₀:"打开此博客文章" U₁:“选择20个选项” U2:“点击撤回” ... U(i):Uᵢ
S₀:余额:100,金额:10S₁:余额:100,金额:20 S 2:余额:80,金额:20 ... S(i):程序(Sᵢ₋₁,Eᵢ)
在这两种情况下,静态信息都可能一个接一个地列出。此外,两个列表都可以沿着相同的离散时间轴i绘制。用户交互以特定顺序U(i)进来,针对上一次运行S(i-1)和事件数据E(i)的结果触发程序功能的运行,以产生S(i)。我们的现实可以被视为状态的时序,就像它可以被视为对象的集合一样。正如面向对象的编程对对象进行建模一样,功能编程对状态的时间序列进行建模。当程序和世界都可以看作是“在系统中流动的信息流”时(SICP第3节),世界可以流入程序,而程序又可以回到世界。
尽管纯功能的独占使用和用户交互的异步性,但Elm程序却微不足道地成为有状态的!这个计数器程序
导入浏览器导入HTML暴露(HTML,按钮,div,文本)导入Html.Event暴露(onClick)main = Browser.sandbox {init = 0,更新=更新,视图=视图} 减量更新消息模型=增量的情况消息-> 模型+ 1减量-> 模型-1视图模型= div [] [按钮[onClick减量] [文本"-" ],div [] [文本(String.fromInt模型)],按钮[onClick增量] [文本" +" ] 在这里可以看到跟踪计数器状态,即使用户当然可以异步单击计数器按钮。 像JavaScript中的垃圾回收一样,Elm从程序员的角度隐藏了专用于异步脚本之间通信的命令性代码。 ↩︎ 有人建议使用一本翻书作为功能编程中状态的一种有价值的心理模型。