为什么bacon.js的可观察对象被分类为惰性序列?



我对惰性序列的理解是,它们在程序访问数据之前不会将数据加载到内存中。所以我可以理解,如果有一个大的数字列表等待被消费,但是序列只有在迭代器调用下一个方法时才从生产者那里提取数据,这是有意义的。

但是,当生产者向可观察对象推送该项时,可观察对象会将该项附加到自己身上。因此,序列不是在消费者请求时加载数据,而是在生产者发送数据时加载数据。那么,可观察对象在哪些方面是懒惰的呢?

在Bacon.js的observable中有两种惰性:

  1. 可观察对象不注册到他们的底层数据源(例如,AJAX获取),直到至少有一个观察者。这种惰性实际上为您提供了自动资源管理,因为到数据源的连接会根据需求自动打开和关闭。此外,如果有多个观察者,则只使用到数据源的单个连接,并共享结果。

  2. 可观察对象不计算传递给map, combine等的函数,直到值被实际使用。因此,如果您在给map的函数中进行昂贵的计算,并且只在一秒钟内对流进行一次采样,则只有这些值才会实际评估。

你没有得到的是反压管理。所以如果你是一个数据源,比如可观察的a,连续产生无限的值,你可以

  • 立即使用a.onValue
  • 处理
  • 使用a.take(1000).onValue先取1000个
  • 使用a.takeUntil((x) -> x > 1000)).onValue直到某些条件

但是你不能影响源生成值的速率,因为Bacon.js没有办法告诉源"我以后对更多的值感兴趣,我会告诉你什么时候"。我一直在考虑添加这样的东西,但这会使事情变得复杂很多。

作为底线,我想说Bacon.js不是处理无限列表的理想库,例如,在Haskell风格中。和AFAIK,也不是任何其他Javascript FRP库。

惰性序列是在可能的最后一次对其元素求值的序列。它们的有用之处在于,您可以传递序列,甚至可以在不计算其内容的情况下对其执行操作。

因为你不计算序列,你甚至可以创建一个无限序列;只要确保不要求整个式子的值。例如,下面的Haskell程序创建一个无限自然数序列,然后将每个元素惰性地乘以2,产生一个无限偶数序列,然后取前5个元素,求值(并且只求值):

take 5 (map (*2) [1..])
-- [2,4,6,8,10]

基本上,使用惰性序列和一组处理它们的函数,如map,您可以编写程序,以可组合的方式创建和处理潜在的无限数据流。

,顺便说一下,这正是(函数式)响应式编程的内容。一个可观测天体可以被看作是一个可能无限的对(value, timestamp)流。对一个可观察对象的操作,比如map,只是在原来的流的基础上创建另一个流。将一个消费者附加到一个可观察对象上,只计算其中"已经发生"的元素,而不计算其他元素。

例如,Bacon的observable.map可以作为一个函数来实现,该函数惰性地将函数应用于流的'value'部分(同样在Haskell中):

map f = Prelude.map ((value, tstamp) -> (f value, tstamp))

observable.delay为'timestamp'部分添加延迟时:

delay dt = Prelude.map ((value, tstamp) -> (value, tstamp + dt))

其中Prelude.map只是惰性序列上的常规"映射"函数。

希望我没有再让你困惑了!

最新更新