阅读FRP(功能反应式编程我很惊讶它看起来有多直观和合乎逻辑 标准 势在必行;然而有一件事让我感到困惑..计算机怎么不立即耗尽内存呢?
从我收集到的[这里],就是在FRP中 完成 价值变化的历史(过去,现在和未来)是 头等舱。这个想法立刻在我脑海里响起一声警告说它已经吃掉了你的记忆 非常 如果在没有立即从内存中清除值的过去的环境中使用它,则快速。
阅读有关 [弗兰],我注意到几个例子已经递归地定义了没有终止条件的函数。如果函数永远不会终止并将其值返回给调用它的函数,它将如何完成任何工作?或者就此而言,一段时间之后怎么没有吹掉堆栈呢?即使是像Haskell这样的惰性语言也会在某些时候遇到堆栈溢出。
对这些事情的解释将不胜感激,因为它完全让我感到困惑。
事实上,这可以用于简单的情况不应该是一个惊喜:由于懒惰和垃圾收集,我们已经在Haskell中舒适地使用无限数据结构。只要您的最终结果不依赖于同时拥有所有值,就可以在您开始时收集它们,或者在第一时间不强制它们。
这就是为什么这个经典的Fibonacci示例在常量¹空间中运行的原因:一旦计算出下两个,就不需要列表中的前一个条目,因此只要你没有任何其他指向列表的指针就会收集它们。
fib n = fibs !! n
where fibs = 0 : 1 : zipWith (+) fibs (drop 1 fibs)
尝试针对不同的输入运行此函数并查看内存使用情况。 (运行它 +RTS -s
。)
(如果您想用图表进行更详细的解释,请查看 这个帖子 我写了。)
关键是,即使程序员可以获得无限量的信息,如果没有别的东西依赖它,我们仍然可以垃圾收集大部分信息。
完全相同的逻辑可用于有效地实施FRP程序。
当然,一切都不那么容易。在里面 fibs
例如,如果我们有一个指向开头的活动指针,内存使用率会上升 fibs
名单。如果您的计算依赖于过多的过去数据,那么FRP也会发生同样的事情:它被称为a 时间泄漏。
处理时间泄漏是实施高效,良好行为的FRP框架的开放性问题之一。很难提供富有表现力的FRP抽象,但不允许使用差的甚至是灾难性的内存。我相信大多数当前的方法最终会提供抽象的FRP类型以及一组不太可能导致这类泄漏的有福操作。一种特别极端的形式是Arrowized FRP,它根本不提供行为/信号类型,而是表达所有内容 转换 信号之间(如箭头所示)。
我自己从未尝试过实施一个好的FRP系统,所以我无法更详细地解释这些问题。如果你对这个主题的更多细节感兴趣,那么一个值得一看的好地方就是Conal Elliott的博客 这个帖子 作为一个很好的起点。你也可以看看他写的一些论文 “推挽功能反应编程” 以及关于这个主题的其他论文,包括一些关于Arrowized FRP的文章 “功能反应式编程,续” (几乎随机选择)。
脚注
¹不是 真 恒定的空间,因为中间结果自己变大。但它 应该 在内存中保持一定数量的列表单元格。
事实上,这可以用于简单的情况不应该是一个惊喜:由于懒惰和垃圾收集,我们已经在Haskell中舒适地使用无限数据结构。只要您的最终结果不依赖于同时拥有所有值,就可以在您开始时收集它们,或者在第一时间不强制它们。
这就是为什么这个经典的Fibonacci示例在常量¹空间中运行的原因:一旦计算出下两个,就不需要列表中的前一个条目,因此只要你没有任何其他指向列表的指针就会收集它们。
fib n = fibs !! n
where fibs = 0 : 1 : zipWith (+) fibs (drop 1 fibs)
尝试针对不同的输入运行此函数并查看内存使用情况。 (运行它 +RTS -s
。)
(如果您想用图表进行更详细的解释,请查看 这个帖子 我写了。)
关键是,即使程序员可以获得无限量的信息,如果没有别的东西依赖它,我们仍然可以垃圾收集大部分信息。
完全相同的逻辑可用于有效地实施FRP程序。
当然,一切都不那么容易。在里面 fibs
例如,如果我们有一个指向开头的活动指针,内存使用率会上升 fibs
名单。如果您的计算依赖于过多的过去数据,那么FRP也会发生同样的事情:它被称为a 时间泄漏。
处理时间泄漏是实施高效,良好行为的FRP框架的开放性问题之一。很难提供富有表现力的FRP抽象,但不允许使用差的甚至是灾难性的内存。我相信大多数当前的方法最终会提供抽象的FRP类型以及一组不太可能导致这类泄漏的有福操作。一种特别极端的形式是Arrowized FRP,它根本不提供行为/信号类型,而是表达所有内容 转换 信号之间(如箭头所示)。
我自己从未尝试过实施一个好的FRP系统,所以我无法更详细地解释这些问题。如果你对这个主题的更多细节感兴趣,那么一个值得一看的好地方就是Conal Elliott的博客 这个帖子 作为一个很好的起点。你也可以看看他写的一些论文 “推挽功能反应编程” 以及关于这个主题的其他论文,包括一些关于Arrowized FRP的文章 “功能反应式编程,续” (几乎随机选择)。
脚注
¹不是 真 恒定的空间,因为中间结果自己变大。但它 应该 在内存中保持一定数量的列表单元格。
关于泄漏部分问题的时间:这确实是实施FRP的主要挑战之一。然而,FRP研究人员和实施者已经找到了几种避免它们的方法。
这完全取决于您为信号提供的精确API。主要问题是您是否提供 更高阶的FRP。这通常采用信号的“monadic join”原语的形式:将信号信号转换为信号的方式,或者换句话说,API用于产生在许多其他信号之间动态切换的信号。这样的API非常强大,但可以引入潜力 时间泄漏,即您询问的问题:需要将所有信号的先前值保留在内存中。然而,正如Heinrich Apfelmus在对先前答案的评论中提到的那样,有一些方法可以通过使用类型系统或其他方式以某种方式限制高阶API来解决这个问题。请参阅该评论以获取进一步解释的链接。
许多FRP库根本不提供更高阶的API,因此(非常容易)避免了时间泄漏的问题。你提到过榆树,就像这种情况一样 这里 在“信号不是榆树的单子”中。这确实是以表现力为代价的,因为没有提供强大的monadic API,但并不是每个人都认为你需要在FRP框架/库中使用这种API的一般功能。
最后,我建议 一个有趣的演讲 作者:Elm的主要作者Evan Czaplicki,他在解释这些问题方面做得非常出色,并提供了解决这些问题的可能方法的概述。他根据FRP方法的解决方法对FRP方法进行了分类。