为持续可测量的现象创建行为 [英] Creating a Behavior for a continuously measurable phenomenon
问题描述
我想从 IO a
创建一个行为ta
,并且使用IO动作的预期语义将在每次行为 sample
d时运行:
{ - 语言FlexibleContexts# - }
import Reflex.Dom
import Control.Monad.Trans
$ b onDemand ::(MonadWidget tm,MonadIO(PullM t))=> IO a - > m(行为ta)
我希望我可以通过执行在
:拉
中测量
onDemand measure = return $ pull(liftIO measure)
然而,结果 Behavior
在初始度量
之后永远不会改变。
我能想出的解决方法是创建一个虚拟的行为
,它可以频繁地更改,然后创建一个假依赖关系:
导入Data.Time.Clock作为时间
hold_ ::(MonadHold tm,Reflex t)=>事件t a - > m(行为t())
hold_ = hold()。 (()<$)
onDemand ::(MonadWidget t m,MonadIO(PullM t))=> IO a - > m(行为t a)
onDemand measure =做
现在< - liftIO Time.getCurrentTime
tick< - hold_ =<<< tickLossy(1/1200)now
return $ pull $ do
_< - sample tick
liftIO measure
然后按预期工作;但既然行为
s只能根据需要采样,这不应该是必要的。
创建一个行为
的正确方法是:对于持续,随时都可观察的现象?
在 Spider
中执行此操作看起来不可能。 内部
提前推理。
在 Spider
Reflex
,其中一个可能的 行为
s将拉取该值。
数据行为a
= BehaviorHold!(保留a)
| BehaviorConst!a
| BehaviorPull!(Pull a)
A Pull
ed值包含如何计算当需要时, pullCompute
和一个缓存的值以避免不必要的重新计算, pullValue
。
data pull a
= Pull {pullValue ::!(IORef(Maybe(PullSubscribed a)))
,pullCompute ::!(BehaviorM a)
}
忽略 BehaviorM
, liftIO
以显而易见的方式提取 IO
计算,它在行为M
需要被抽样。在 Pull
中,您的行为会被观察一次,但不会被重新观察,因为缓存的值不会失效。
缓存值 PullSubscribed一个
包含值 a
,如果此值无效,则需要使其他值无效的列表以及一些无聊的内存管理的东西。
pre $ data pullSubscribed a
= PullSubscribed {pullSubscribedValue ::!a
, pullSubscribedInvalidators ::!(IORef [Weak Invalidator])
- ...无聊的内存
}
Invalidator
是一个量化的 Pull
,足以获得内存引用t o递归地读取invalidators来使失效并将缓存值写入 Nothing
。
能够不断使我们自己的 BehaviorM
失效。执行时,传递给 BehaviorM
的环境具有自己的invalidator副本,该副本由 BehaviorM
在它们自己变为无效时使它无效。
从内部执行 readBehaviorTracked
似乎没有办法行为本身的失效者( wi
)最终会在取样者列表中被取消( invsRef
)。
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)
实例或通过修复 pull
和 sample
:
onDemand ::(Reflex t,MonadIO(PullM t))=> IO a - >行为ta
onDemand read = b
其中
b =拉出去
去=做
样品b
liftIO读取
我没有 Reflex
环境来尝试此操作,但我不'我认为结果会很好。
I would like to create a Behavior t a
from an IO a
, with the intended semantics that the IO action would be run every time the behavior is sample
d:
{- language FlexibleContexts #-}
import Reflex.Dom
import Control.Monad.Trans
onDemand :: (MonadWidget t m, MonadIO (PullM t)) => IO a -> m (Behavior t a)
I hoped I could do this by just executing the measurement
in a pull
:
onDemand measure = return $ pull (liftIO measure)
However, the resulting Behavior
never changes after an initial measure
ment.
The workaround I could come up with was to create a dummy Behavior
that changes "frequently enough" and then create a fake dependency on that:
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
This then works as expected; but since Behavior
s can only be sampled on demand anyway, this shouldn't be necessary.
What is the correct way to create a Behavior
for a continuous, observable-at-any-time phenomenon?
Doing this in Spider
looks impossible. Internal
reasoning ahead.
In the Spider
implementation of Reflex
, one of the possible Behavior
s is to pull the value.
data Behavior a
= BehaviorHold !(Hold a)
| BehaviorConst !a
| BehaviorPull !(Pull a)
A Pull
ed value consists of how to compute the value when needed, pullCompute
, and a cached value to avoid unnecessary re-computation, pullValue
.
data Pull a
= Pull { pullValue :: !(IORef (Maybe (PullSubscribed a)))
, pullCompute :: !(BehaviorM a)
}
Ignoring the ugly environment of BehaviorM
, liftIO
lifts an IO
computation the obvious way, it runs it when the BehaviorM
needs to be sampled. In the Pull
, your behavior is observed once but isn't re-observed because the cached value isn't invalidated.
The cached value PullSubscribed a
consists of the value a
, a list of other values that need to be invalidated if this value is invalidated, and some boring memory management stuff.
data PullSubscribed a
= PullSubscribed { pullSubscribedValue :: !a
, pullSubscribedInvalidators :: !(IORef [Weak Invalidator])
-- ... boring memory stuff
}
An Invalidator
is a quantified Pull
that's enough to get the memory reference to recursively read the invalidators to invalidate and write the cached value to Nothing
.
To pull constantly we'd like to be able to constantly invalidate our own BehaviorM
. When executed, the environment passed to the BehaviorM
has a copy of its own invalidator, which is used by dependencies of the BehaviorM
to invalidate it when they themselves become invalid.
From the internal implementation of readBehaviorTracked
there seems to be no way that the behavior's own invalidator (wi
) can ever end up in the list of subscribers that are invalidated when it is sampled (invsRef
).
a <- liftIO $ runReaderT (unBehaviorM $ pullCompute p) $ Just (wi, parentsRef)
invsRef <- liftIO . newIORef . maybeToList =<< askInvalidator
-- ...
let subscribed = PullSubscribed
{ pullSubscribedValue = a
, pullSubscribedInvalidators = invsRef
-- ...
}
Outside of the internals, if there does exist a way to constantly sample a Behavior
it would involve a MonadFix (PullM t)
instance or mutual recursion through fixing of pull
and sample
:
onDemand :: (Reflex t, MonadIO (PullM t)) => IO a -> Behavior t a
onDemand read = b
where
b = pull go
go = do
sample b
liftIO read
I don't have a Reflex
environment to try this in, but I don't think the results will be pretty.
这篇关于为持续可测量的现象创建行为的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!