【问题标题】:Advantage of Functional Reactive Programming over event-listeners函数响应式编程相对于事件监听器的优势
【发布时间】:2014-05-24 18:12:27
【问题描述】:

我听说过很多关于函数式反应式编程的内容,并决定看看有什么大不了的。通过 bacon.js 文档,似乎主要区别在于,我没有在组件上设置事件侦听器,而是在其上创建事件流,并将事件处理程序传递到流中。换句话说,我真正做的只是将事件处理程序从组件移动到事件流。是这样吗?如果是这样,这样做的最大优势是什么?

【问题讨论】:

  • 嗯,landing page 上的示例对我来说似乎更具声明性和健壮性。无需编写在 #text 中侦听 keydown 的代码,并注册和取消超时以忽略输入,然后在 300 毫秒内进行更多输入,并维护最近输入的列表以不向服务器发送不必要的请求,您只需指定:我希望 keydown 事件之后有 300 毫秒的暂停,但请不要重复。逻辑越复杂,对比就越强烈。

标签: javascript event-handling functional-programming frp bacon.js


【解决方案1】:

关于函数响应式编程 (FRP) 的关键点是句法属性:

值的动态行为在声明时指定

例如,考虑一个可以通过按下按钮向上或向下计数的计数器。在命令式风格中,你可能会这样写:

counter := 0                               -- initial value
on buttonUp   = (counter := counter + 1)   -- change it later
on buttonDown = (counter := counter - 1)

首先,用初始值声明计数器。然后,在代码的后面部分,您更改该值。现在,如果有人问您“在任何给定时刻,counter 的值是多少?”,您必须查看引用名称 counter 的代码的所有部分,因为它可以改变。

相比之下,当使用 FRP 样式代码时,可以通过查看代码中的一个位置来回答问题:声明counter 的位置。例如,在 Haskell 中,您可以将计数器写为

counter :: Behavior Int
counter = accumulate ($) 0
            (fmap (+1) buttonUp
             `union` fmap (subtract 1) buttonDown)

右侧包含您需要了解counter 在任何给定时刻的值的所有信息。特别是,您看到它可以通过buttonUpbuttonDown 进行更改,就是这样。您不必筛选代码,寻找计数器可能更改的地方,不,只需查看其声明并从那里跟进。

这就是为什么 FRP 代码比基于事件的意大利面条代码更不容易出错的原因。

另见一些初步的documentation for my threepenny-gui library

【讨论】:

  • "counter 的值是多少?您必须查看引用名称计数器的所有代码部分,因为那是可以更改的地方。" -- 除非您没有将counter 声明为某个全局变量,而是将其封装在具有明确定义接口的某个对象中。这样一来,一个简单的counter.getValue() 就可以非常可靠地解决问题......
  • @rodrigo-silveira 我的意思是,为了知道counter.getValue() 返回的内容,您必须在代码中找到所有出现的counter.setValue(…)。相比之下,使用 FRP,这些事件都可以从 counter 的声明中获得。
  • 所以重点是 1.) 能够在一个地方表达变化的价值,对吧? 2.)为了实现这一点,这个“一个地方”必须有权访问价值所依赖的所有相关信息 - >以便您获取信息的地方必须是列表(流),因此它可以携带不同类型的信息件(事件)。 3.)假设我们有列表(流),我们也可以在更高级别上操作它们(检查@Bergi 的答案),但这不是主要目的或初衷,而是此设置启用的有用巧合和功能。还是它的主要功能?
【解决方案2】:

是吗?

没有。这是关于拥有事件流。最后你仍然会为它们附加监听器来执行效果,但是在源和目标之间你有一个非常强大的抽象。

这样做有什么好处?

事件流确实有很多 higher-order functions 可以轻松处理它们,并且为了组合它们而无需写出所有容易出错的样板代码。

作为the docs put it 非常好,培根

通过从命令式切换到函数式,将您的活动意大利面变成干净声明式风水培根。这就像用 mapfilter 之类的函数式编程概念替换嵌套的 for-loops。停止处理单个事件并改为使用事件流。将您的数据与mergecombine 结合起来[并使用] 更重的武器[例如] flatMapcombineTemplate

【讨论】:

  • 那么优点是对事件流进行转换比嵌套事件处理程序更干净?
  • 是的。具有干净代码的所有优点:可理解性、可读性、无错误、简洁。借助 frp 定义明确的语义(在 lib 中实现),还可以推理代码并证明正确性。
  • 不仅是转换(比如简单的filter/map/reduce 操作),还有可组合性,这是函数式编程的关键。
  • Promises 也解决了 spagetti 代码问题。他们登陆页面上的示例非常基本,看起来比使用 AngularJS 完成的相同简单功能更冗长。有没有比 Promise/Angular 方法更好、更简洁的代码的真实示例?
  • @PEZO 引用中提到:干净和声明性的函数式编程。当然,我可能没有像 Heinrich Apfelmus 那样雄辩地表达它,但它确实意味着同样的事情。我没有窃取他的话,而是赞成他的回答:-) 不,没有权威可以为 FRP 一词给出一个真实的定义。