我从Seven More Languages in Seven Weeks了解榆树。以下示例让我困惑:
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
在我看来:
presses
的初始值仅在0
main
main
之后,presses
的初始值似乎是函数(a -> b -> b)
的结果,或示例中的(\dir presses -> presses + dir.x)
的结果,以前的评估。如果情况确实如此,那么这是否违反了函数式编程原则,因为main现在维持内部状态(或者至少foldp
确实如此)?
当我在代码中的多个位置使用foldp
时,这是如何工作的?它是否保留多个内部状态,每次使用一个状态?
我看到的唯一其他选择是foldp
(在示例中)从0开始计数,也就是说,每次评估时,都会以某种方式折叠{{1}提供的整个历史记录。 1}}。在我看来,这非常浪费,并且肯定会导致长时间运行时出现内存异常。
我在这里错过了什么吗?
答案 0 :(得分: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' s State a
类型,但它实际上取决于您的观点)。
但是什么是可变状态?什么是可变值?它是程序内部的一个值,是可变的。也就是说,它可以改变。在不同的时间它可以是不同的东西。啊,但我们知道Elm如何随着时间的推移而改变价值观!那是Signal
所以榆树中的Signal
实际上是一个可以随时间变化的值,因此可以看作变量,可变值或可变状态。只是我们通过在Signal
上只允许一些精心选择的操作来严格管理这个值。这样的Signal
可以基于程序中的其他Signal
,或来自图书馆或来自外部世界(考虑像Mouse.position
这样的输入)。谁知道外界怎么想出那个信号呢!因此,允许您自己的Signal
基于Signal
的过去值实际上是可以的。
您可以将Signal
视为可变状态的安全包装。我们假设来自外部世界的信号(作为您的程序的输入)是不可预测的,但是因为我们有这个安全包装器只允许提升/采样/过滤器/折叠,所以您编写的程序是完全可预测的。包含和管理副作用,因此我认为它仍然是功能性编程"。
答案 1 :(得分:10)
您将实现细节与概念细节混淆。每种函数式编程语言最终都会被转换为汇编代码,这显然是必不可少的。这并不意味着你不能在语言水平上保持纯洁。
不要认为main
被重复评估,每次都会返回不同的结果。概率上,Signal
是无限的值列表。 main
将无限的键盘箭头列表作为输入,并将其转换为无限的元素列表。给定相同的箭头列表,它将始终返回完全相同的元素列表,没有副作用。在这个抽象层次上,它是一个纯函数。
现在,碰巧我们只对序列的最后一个元素感兴趣。这允许在实现中进行一些优化,其中一个是存储累积值。重要的是,实施是参考透明的。从语言的角度来看,你得到了完全相同的答案,就好像你存储了整个序列一样,并且每次将值添加到结尾时都从头开始重新计算。给定相同的输入,您获得相同的输出。唯一的区别是存储空间和执行时间。
换句话说,函数式编程的整个想法不是消除状态跟踪,而是将其从程序员的范围中抽象出来。程序员可以在理想的世界中发挥作用,而编译器和运行时从属于可变状态的下水道,可以为我们其他人提供理想的世界。
答案 2 :(得分:3)
你应该注意“不保持内部状态”并不是FP的真正强大定义。它更像是一个实现约束。我更喜欢的定义是“用纯函数构建”。如果没有潜水深入,用简单的英语表示当给定相同的输入时,所有函数都返回相同的输出。这个定义与以前不同,为您提供了巨大的推理能力和一种简单的方法来检查某个程序是否遵循它,同时在当前硬件上保留一些优化空间。
鉴于重新制定的限制,只要用纯函数建模,函数式语言就可以自由使用mutable。回答你的问题,榆树程序是用纯函数构建的,所以它可能是一种函数式语言。 Elm使用特殊的数据结构Signal来模拟外部世界的交互和内部状态,以及任何其他功能语言。