11

我想Behavior t a从 an创建一个IO a,其预期语义是每次行为为sampled 时都会运行 IO 操作:

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

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

我希望我可以通过执行measurementin a来做到这一点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

然后按预期工作;但由于Behaviors 无论如何只能按需采样,因此没有必要这样做。

创建一个Behavior连续的、随时可观察的现象的正确方法是什么?

4

2 回答 2

4

这样做Spider看起来是不可能的。Internal提前推理。

在 的Spider实现中Reflex,可能Behavior的 s 之一是拉取值。

data Behavior a
   = BehaviorHold !(Hold a)
   | BehaviorConst !a
   | BehaviorPull !(Pull a)

ed值Pull包括如何在需要时计算该值pullCompute,以及一个缓存值以避免不必要的重新计算,pullValue

data Pull a
   = Pull { pullValue :: !(IORef (Maybe (PullSubscribed a)))
          , pullCompute :: !(BehaviorM a)
          }

忽略 的丑陋环境BehaviorM,以显而易见的方式liftIO提升IO计算,它在BehaviorM需要采样时运行它。在 中Pull,您的行为会被观察一次,但不会被重新观察,因为缓存的值没有失效。

缓存的值PullSubscribed a由 value a、如果该值无效则需要无效的其他值的列表以及一些无聊的内存管理内容组成。

data PullSubscribed a
   = PullSubscribed { pullSubscribedValue :: !a
                    , pullSubscribedInvalidators :: !(IORef [Weak Invalidator])
                    -- ... boring memory stuff
                    }

AnInvalidator是一个量化Pull的,足以让内存引用递归地读取无效器以使缓存值无效并将缓存值写入Nothing.

为了不断地拉动,我们希望能够不断地使我们自己的BehaviorM. 执行时,传递给 的环境BehaviorM具有其自己的无效器的副本,当它们自己变得无效时,它被依赖项使用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
          -- ...
          }

在内部结构之外,如果确实存在一种不断采样 a 的方法Behavior,它将涉及一个MonadFix (PullM t)实例或通过修复pulland的相互递归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

我没有Reflex环境可以尝试这个,但我认为结果不会很好。

于 2016-03-13T08:27:54.520 回答
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尽可能晚地使用无效器运行是很重要的,以便它使现有事物无效。

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

更新那些真正想要实现这个的人:上面的代码可能会在一些复杂的设置中死锁。我的解决方案是在单独的线程中计算本身之后稍微执行失效。 这是完整的代码片段。链接的解决方案似乎工作正常(现在在生产中使用了将近一年)。

于 2017-07-20T09:31:19.897 回答