鉴于您的示例用例,我认为如果您远离fromPoll
. 为了解释原因,需要做一些澄清。(注意:在下文中,“流”指的是一个Event t a
,而“发生”指的是组成它们的触发之一。)
但是,使用事件轮询行为(通过<@
等)为我提供了前一个事件的行为值,而不是当前值。
我想您是在暗指以下文档中的解释,例如stepper
:
请注意,比较中的小于号timex < time
表示行为的值在事件发生“稍微之后”发生变化。这允许递归定义。
但是,该延迟仅与用于定义行为的流(即您传递给stepper
/accumB
的流)以及与之同步的任何流有关。例如,假设您有两个独立的流eTick
和eTock
,以及以下网络片段:
eIncrement = (+1) <$ eTick
bCount = accumB 0 eIncrement
eCountTick = bCount <@ eTick
eCountTock = bCount <@ eTock
eIncrement
并且与eCountTick
同步eTick
,因此观察到的值eCountTick
是“旧”值;即同步更新前的值。然而,从 给出的观点来看eCountTock
,这些都不重要。对于使用 的观察者eCountTock
来说,没有延迟可言,并且值始终是当前值。
从中观察到的行为fromPoll
不能依赖于它们自己,因此不能通过在触发此事件之前而不是仅在前一个事件触发之后观察行为来引入循环。
我们只关心与更新行为的流同步。因此,就观察值而言,“就在下一次发生之前”和“就在上一次发生之后”归结为同一件事。fromPoll
,但是,这使事情变得有些混乱。它创建了一个行为,每当事件网络中发生任何事件时都会更新该行为;因此更新与所有流的联合同步。没有独立于fromPoll
事件的流这样的东西,因此观察到的值将受到延迟的影响,但我们观察它。那样的话,fromPoll
对于应用程序驱动时钟来说是行不通的,因为它需要以一定的精度跟踪连续变化。
以上所有内容都暗示反应香蕉没有内置的时间概念。每个流中只有“逻辑”时间线,可以通过合并流来交织。因此,如果我们想要一个当前时间行为,我们最好的选择是从一个独立的流中构建一个。这是该方法的演示,只要精度threadDelay
允许,它将产生新鲜和及时的结果:
{-# LANGUAGE RankNTypes #-}
module Main where
import Control.Concurrent
import Control.Monad
import Data.Time
import Reactive.Banana
import Reactive.Banana.Frameworks
main = do
let netDesc :: forall t. Frameworks t => Moment t ()
netDesc = do
(eTime, fireTime) <- newEvent
liftIO . forkIO . forever $
threadDelay (50 * 1000) >> getCurrentTime >>= fireTime
bTime <- flip stepper eTime <$> liftIO getCurrentTime
(eTick, fireTick) <- newEvent
liftIO . forkIO . forever $
threadDelay (5000 * 1000) >> fireTick ()
reactimate $ print <$> bTime <@ eTick
network <- compile netDesc
actuate network >> threadDelay (52000 * 1000) >> pause network
bTime
eTime
每0.05s更新一次;它是通过 观察到eTick
的,一个独立的流eTime
,每 5 秒出现一次。然后,您可以使用eTick
从它派生的流来观察和更新您的实体。或者,您可以结合bTime
应用样式中的实体行为来获取,例如,最新 ping 的行为,以使用eTick
.
在您的情况下,具有规范的时间行为看起来像是一种合理的方法;它在概念上是清晰的,并且很容易概括为多个刻度。在任何情况下,您可以使用的其他方法包括摆脱bTime
并eTick
用作低分辨率的当前时间流(尽管这似乎会使threadDelay
错误建立得更快),以及摆脱eTick
使用changes
来获得新鲜的流从行为中更新值(通过它带有自己的怪癖和烦恼,正如文档所暗示的那样)。