我想从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
的正确方法是什么?
答案 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
进行抽样的方法,那么通过修复pull
和sample
将涉及 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。 链接的解决方案似乎正常工作(现在在生产中使用它近一年)。