16

我正在七周内从七种语言中学习榆树。以下示例使我感到困惑:

import Keyboard
main = lift asText (foldp (\dir presses -> presses + dir.x) 0 Keyboard.arrows)

foldp定义为:

Signal.foldp : (a -> b -> b) -> b -> Signal a -> Signal b

在我看来:

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

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

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

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

我在这里错过了什么吗?

4

3 回答 3

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

但是,如果您两次使用来自 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

主要问题

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

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

但是什么是可变状态?什么是可变值?它是程序内部的一个值,是可变的。也就是说,它可以改变。在不同的时间可能是不同的事情。啊,但我们知道 Elm 如何在随时间变化时调用值!那是一个Signal
所以实际上SignalElm 中的 a 是一个可以随时间变化的值,因此可以被视为变量、可变值或可变状态。只是我们非常严格地管理这个值,只允许对Signals 进行一些精心挑选的操作。这样的 aSignal可以基于Signal您程序中的其他 s,或者来自库或来自外部世界(想想像这样的输入Mouse.position)。谁知道外面的世界是怎么想出那个信号的!所以允许你自己Signal的 s 基于过去的价值Signals其实还可以。

结论 / TL;DR

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

于 2014-07-26T10:49:03.653 回答
10

您将实现细节与概念细节混淆了。每种函数式编程语言最终都会被翻译成汇编代码,这绝对是必不可少的。这并不意味着你不能在语言层面上保持纯洁。

不要认为main是被反复评估,每次返回不同的结果。ASignal在概念上是一个无限的值列表。 main将无限的键盘箭头列表作为输入,并将其转换为无限的元素列表。给定相同的箭头列表,它将始终返回完全相同的元素列表,没有副作用。因此,在这个抽象级别,它是一个纯函数。

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

换句话说,函数式编程的整个思想不是消除状态跟踪,而是将其从程序员的范围中抽象出来。程序员可以在理想世界中玩耍,而编译器和运行时则在可变状态的下水道中奴隶,为我们其他人创造理想世界。

于 2014-09-12T16:24:35.233 回答
3

您应该注意,“不维护内部状态”并不是 FP 的真正强定义。它更像是一个实现约束。我更喜欢的定义是“由纯函数构建”。无需深入研究,用简单的英语来说,这意味着所有函数在给定相同输入时都返回相同的输出。与以前不同,此定义为您提供了强大的推理能力和一种简单的方法来检查某些程序是否遵循它,同时在当前硬件上保留一些优化空间。

鉴于重新制定的限制功能语言可以自由使用可变对象,只要它使用纯函数建模即可。回答您的问题,elm 程序由纯函数构建,因此它可能是一种函数式语言。Elm 使用特殊的数据结构 Signal 来模拟外部世界的交互和内部状态,就像任何其他函数式语言一样。

于 2014-07-27T07:11:05.777 回答