处理活性香蕉中的当前时间

Tom*_*lis 6 haskell frp reactive-banana

你如何应对当前反应香蕉的时间?

理想情况下,我希望有一个Behaviour我可以"轮询"以获得当前时间.但是,BehaviourEvents(通过<@等)进行轮询给出Behaviour前一个 Event值,而不是当前值.(我意识到这是为了避免确实有用的循环定义.)

我发现fromPoll我认为会有所帮助. Behaviour这是从观察到的小号fromPoll不能依靠自己,因此没有周期可以通过观察行为出台之前这个Event被解雇,而不是刚过以前Event解雇.

一个题外话

在某些更正式的术语中,我建议Events总是在时间t +发生,并且Behaviours总是在时间t观察到-Event观察在它们之前无限短时间内发生的行为.新价值Behaviour的生成S- accumB和朋友们总是准时开始T +所以无法通过观察EventS的也发生在时间T +.

在这个建议的语义下Behaviour,fromPoll将在每个Event处理之前更新.其他Behaviours将在之后更新,因为它们是由accumB朋友创建的.

我的用例

无论如何,这对我的主要问题来说是一个重要的题外话.我想知道是否有某种方式来处理当前时间(而不是时间以前 Event在反应香蕉).例如,我的用例是跟踪实体发送的ping,以及它们中的任何一个是否在特定时间间隔内没有发送ping来发出警告事件.

当然,我可以并且会非常频繁地发射事件,所以我的警告不会大量错误.然而,它似乎是一个瑕疵,他们不能准确.

处理这个问题的正确方法是什么?

dup*_*ode 2

鉴于您的示例用例,我认为如果您远离fromPoll. 为了解释原因,需要进行一些澄清。(注意:在下文中,“流”指的是一个Event t a,“发生”指的是组成它们的触发之一。)

然而,通过事件轮询行为(通过<@等)给我的是前一个事件的行为值,而不是当前值。

我想您是指来自以下文档的解释stepper

请注意,比较中的小于号timex < time意味着行为值在事件发生“稍晚”后发生变化。这允许递归定义。

然而,这种延迟仅涉及用于定义行为的流(即传递给stepper/ 的accumB流)以及与其同步的任何流。例如,假设您有两个独立的流eTickeTock,以及以下网络片段:

eIncrement = (+1) <$ eTick
bCount = accumB 0 eIncrement
eCountTick = bCount <@ eTick

eCountTock = bCount <@ eTock
Run Code Online (Sandbox Code Playgroud)

eIncrementeCountTick与 同步eTick,因此观察到的值eCountTick是“旧”值;即同步更新前的值。然而,从 给出的观点来看eCountTock,这些都不重要。对于使用 的观察者来说eCountTock,没有延迟可言,并且该值始终是当前值。

观察到的行为不能依赖于自身,因此通过观察此事件触发之前而不是上一个事件触发之后fromPoll的行为,不会引入循环。

我们只关心与更新行为的流同步的流。因此,就观察值而言,“就在下一次出现之前”和“就在上一次出现之后”归结为同一件事。fromPoll然而,这让事情变得相当混乱。它创建一个行为,每当事件网络中发生任何事件时都会更新该行为;因此更新与所有流的并集同步。不存在独立于fromPoll事件的流,因此无论我们如何观察,观察到的值都会受到延迟的影响。因此,fromPoll不适用于应用程序驱动时钟,它需要以一定的精度跟踪连续变化。

上述所有内容隐含的是,reactive-banana 没有内置的时间概念。每个流中只有“逻辑”时间线,可以通过合并流来交织。因此,如果我们想要当前的时间行为,我们最好的选择是从独立的流中构建一个行为。以下是该方法的演示,它将在允许的精度范围内产生新鲜且及时的结果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
Run Code Online (Sandbox Code Playgroud)

bTime每0.05s更新一次eTime;它是通过 观察到的eTick,一个独立于每 5 秒发生一次的流eTime。然后,您可以使用eTick从它派生的流来观察和更新您的实体。或者,您可以bTime以应用方式组合实体行为,以获取要观察的最新 ping 的行为eTick

对于您的情况来说,采用规范的时间行为似乎是一种合理的方法;它在概念上很清晰,并且很容易推广到多个刻度。在任何情况下,您可以使用的其他方法包括摆脱bTime并用作eTick低分辨率的当前时间流(尽管这似乎会使错误建立得更快),以及通过使用来获得新鲜的流来threadDelay摆脱更新行为的值(正如文档所暗示的那样,它有自己的怪癖和烦恼)。 eTickchanges