为持续可测量的现象创建行为

时间:2016-03-12 12:10:07

标签: haskell ghcjs reflex

我想从Behavior t a创建一个IO a,其中包含每次行为sample时都会运行IO操作的预期语义d:

{- language FlexibleContexts #-}
import Reflex.Dom
import Control.Monad.Trans

onDemand :: (MonadWidget t m, MonadIO (PullM t)) => IO a -> m (Behavior t a)

我希望我可以通过执行measurement中的pull来完成此操作:

onDemand measure = return $ pull (liftIO measure)

但是,生成的Behavior在初始measure之后永远不会更改。

我能想到的解决方法是创建一个虚拟Behavior,它“经常”更改“然后创建一个假的依赖:

import Data.Time.Clock as Time

hold_ :: (MonadHold t m, Reflex t) => Event t a -> m (Behavior t ())
hold_ = hold () . (() <$)

onDemand :: (MonadWidget t m, MonadIO (PullM t)) => IO a -> m (Behavior t a)
onDemand measure = do
    now <- liftIO Time.getCurrentTime
    tick <- hold_ =<< tickLossy (1/1200) now
    return $ pull $ do
        _ <- sample tick
        liftIO measure

然后按预期工作;但由于Behavior只能按需采样,所以这不是必需的。

为连续,可观察的任何时间现象创建Behavior的正确方法是什么?

2 个答案:

答案 0 :(得分:4)

Spider中执行此操作看起来不可能。 Internal提前推理。

Spider的{​​{1}}实施中,可能的Behavior之一是提取值。

Reflex

Pull ed值包括如何在需要时计算值data Behavior a = BehaviorHold !(Hold a) | BehaviorConst !a | BehaviorPull !(Pull a) 和缓存值,以避免不必要的重新计算,pullCompute

pullValue

忽略BehaviorM的丑陋环境,data Pull a = Pull { pullValue :: !(IORef (Maybe (PullSubscribed a))) , pullCompute :: !(BehaviorM a) } 以明显的方式提升liftIO计算,当需要对IO进行采样时,它会运行它。在BehaviorM中,您的行为会被观察一次,但不会被重新观察,因为缓存的值不会失效。

缓存的值PullSubscribed a包含值Pull,如果此值无效则需要失效的其他值的列表,以及一些无聊的内存管理内容。

a

Invalidator是一个量化的data PullSubscribed a = PullSubscribed { pullSubscribedValue :: !a , pullSubscribedInvalidators :: !(IORef [Weak Invalidator]) -- ... boring memory stuff } ,它足以让内存引用以递归方式读取无效符,使缓存值无效并将缓存值写入Pull

为了不断拉动,我们希望能够不断使我们自己的Nothing无效。执行时,传递给BehaviorM的环境有一个自己的invalidator副本,BehaviorM的依赖项使用它来使它们自身无效时无效。

readBehaviorTracked的内部实现中,行为自身的无效者(BehaviorM)似乎无法在采样时失效的订阅者列表中结束({{ 1}})。

wi

在内部之外,如果确实存在一种不断对invsRef进行抽样的方法,那么通过修复pullsample将涉及 a <- liftIO $ runReaderT (unBehaviorM $ pullCompute p) $ Just (wi, parentsRef) invsRef <- liftIO . newIORef . maybeToList =<< askInvalidator -- ... let subscribed = PullSubscribed { pullSubscribedValue = a , pullSubscribedInvalidators = invsRef -- ... } 实例或相互递归:

Behavior

我没有MonadFix (PullM t)环境试试这个,但我认为结果不会很好。

答案 1 :(得分:2)

我已经尝试了一段时间并找到了解决方法。它似乎与迄今为止的最新版本的反射一起使用。诀窍是每次评估给定的IO操作时强制使缓存的值无效。

import qualified Reflex.Spider.Internal as Spider

onDemand :: IO a -> Behavior t a
onDemand ma = SpiderBehavior . Spider.Behavior
            . Spider.BehaviorM . ReaderT $ computeF
  where
    computeF (Nothing, _) = unsafeInterleaveIO ma
    computeF (Just (invW,_), _) = unsafeInterleaveIO $ do
        toReconnect <- newIORef []
        _ <- Spider.invalidate toReconnect [invW]
        ma

使用unsafeInterleaveIO尽可能晚地运行invalidator非常重要,这样它就会使现有的东西无效。

此代码还有另一个问题:我忽略了toReconnect引用和invalidate函数的结果。在当前版本的反射中,后者总是空的,因此不应该引起任何问题。 但我不确定toReconnect:从代码中看来,如果它有一些订阅的开关,如果处理不当,它们可能会中断。虽然我不确定这种行为是否可以订阅或不订阅。

对于那些真正想要实现此目的的人来说,

更新: 上面的代码可以在一些复杂的设置中死锁。 我的解决方案是在计算本身后在一个单独的线程中稍微执行失效。 Here is the complete code snippet。 链接的解决方案似乎正常工作(现在在生产中使用它近一年)。