'foldp'是否违反FP的无可变状态原则?

Mar*_*cel 16 functional-programming elm

在七周内七种语言中学习榆树.以下示例让我困惑:

import Keyboard
main = lift asText (foldp (\dir presses -> presses + dir.x) 0 Keyboard.arrows)
Run Code Online (Sandbox Code Playgroud)

foldp 定义为:

Signal.foldp : (a -> b -> b) -> b -> Signal a -> Signal b
Run Code Online (Sandbox Code Playgroud)

在我看来:

  • 累加器的初始值presses0在第一次评估时main
  • 在第一次评估之后main,似乎初始值presses是函数的结果(a -> b -> b),或者(\dir presses -> presses + dir.x)在示例中,是在先前的评估中.

如果确实如此,那么这不是违反函数式编程原则,因为main现在维持内部状态(或者至少foldp是这样)吗?

当我foldp在代码中的多个位置使用时,这是如何工作的?它是否保留多个内部状态,每次使用一个状态?

我看到的唯一另一种选择是foldp(在示例中)从0开始计数,也就是说,每次评估它,并以某种方式折叠由提供的整个历史记录Keyboard.arrows.在我看来,这非常浪费,并且肯定会导致长时间运行时出现内存异常.

我在这里错过了什么吗?

Apa*_*hka 20

这个怎么运作

是的,foldp保持一些内部状态.保存整个历史将是浪费而且没有完成.

如果foldp在代码中多次使用,执行不同的操作或具有不同的输入信号,则每个实例将保持其自己的本地状态.例:

import Keyboard

plus  = (foldp (\dir presses -> presses + dir.x) 0 Keyboard.arrows)
minus = (foldp (\dir presses -> presses - dir.x) 0 Keyboard.arrows)
showThem p m = flow down (map asText [p, m])
main  = lift2 showThem plus minus
Run Code Online (Sandbox Code Playgroud)

但是如果你使用foldp的结果信号两次,只有一个foldp实例将在你编译的程序中,结果的变化只会在两个地方使用:

import Keyboard

plus  = (foldp (\dir presses -> presses + dir.x) 0 Keyboard.arrows)
showThem p m = flow down (map asText [p, m])
main  = lift2 showThem plus plus
Run Code Online (Sandbox Code Playgroud)

主要问题

如果确实如此,那么这不是违反函数式编程原则,因为main现在维持内部状态(或者至少foldp是这样)吗?

函数式编程没有一个每个人都使用的伟大的规范定义.有许多函数式编程语言的例子允许使用可变状态.其中一些编程语言向您显示类型系统中的值是可变的(您可以看到Haskell的State a类型,但它实际上取决于您的观点).

但是什么是可变状态?什么是可变值?它是程序内部的一个值,是可变的.也就是说,它可以改变.在不同的时间它可以是不同的东西.啊,但我们知道榆树如何随着时间的推移而改变价值观!那是一个Signal.
所以Signal在Elm中确实是一个可以随时间变化的值,因此可以被视为变量,可变值或可变状态.只是我们非常严格地管理这个值,只允许对Signals 进行一些精心选择的操作.这样的a Signal可以基于Signal程序中的其他s,或者来自图书馆或来自外部世界(想想输入等Mouse.position).谁知道外界怎么想出那个信号呢!所以允许你自己的Signals基于s的过去值Signal实际上是可以的.

结论/ TL; DR

你可以看到Signal围绕可变状态的安全包装.我们假设来自外部世界的信号(作为您的程序的输入)是不可预测的,但是因为我们有这个安全包装器只允许提升/采样/过滤器/折叠,所以您编写的程序是完全可预测的.包含和管理副作用,因此我认为它仍然是"函数式编程".

  • 难道你不会说'a`同样神奇的展开,也发生在`Signal.lift:(a - > b) - >信号a - >信号b`?无论如何,我明白它可能会有点奇怪.你看过信号图了吗?它们在https://www.seas.harvard.edu/sites/default/files/files/archived/Czaplicki.pdf的第4节中进行了解释.在那里你看到一个foldp节点有内部状态.但你也可以看到它的不同,作为一个紧凑的循环,其中foldp使用它的输出信号作为输入信号:https://groups.google.com/group/elm-discuss/attach/4b33ca584290b011/elm_fold.svg?part = 0.1 &AUTHUSER = 0 (2认同)

Kar*_*ldt 10

您将实现细节与概念细节混淆.每种函数式编程语言最终都会被转换为汇编代码,这显然是必不可少的.这并不意味着你不能在语言水平上拥有纯洁.

不要认为main被重复评估,每次都会返回不同的结果.A Signal在概念上是一个无限的值列表. main将无限的键盘箭头列表作为输入,并将其转换为无限的元素列表.给定相同的箭头列表,它将始终返回完全相同的元素列表,没有副作用.在这个抽象层次上,它是一个纯粹的功能.

现在,碰巧我们只对序列的最后一个元素感兴趣.这允许在实现中进行一些优化,其中之一是存储累积值.重要的是,实施是参考透明的.从语言的角度来看,你得到了完全相同的答案,就好像你存储了整个序列一样,并且每次将值添加到结尾时都从头开始重新计算.给定相同的输入,您获得相同的输出.唯一的区别是存储空间和执行时间.

换句话说,函数式编程的整个想法不是消除状态跟踪,而是将其从程序员的范围中抽象出来.程序员可以在理想的世界中发挥作用,而编译器和运行时从属于可变状态的下水道,可以为我们其他人提供理想的世界.