Создание поведения для постоянно измеряемого явления

Я хотел бы создать Behavior t a из IO aс предполагаемой семантикой, что действие ввода-вывода будет выполняться каждый раз, когда поведение sampleд:

{- 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 ответа

Решение

Делать это в Spider выглядит невозможно. Internal рассуждения впереди.

в Spider реализация Reflex один из возможных Behavior s, чтобы получить значение.

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

Pull ed значение состоит из того, как вычислить значение при необходимости, pullCompute и кэшированное значение, чтобы избежать ненужных повторных вычислений, pullValue,

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

Игнорирование уродливой среды BehaviorM, liftIO поднимает IO вычисления очевидным образом, он запускается, когда BehaviorM должен быть выбран. в Pull ваше поведение наблюдается один раз, но не наблюдается повторно, потому что кэшированное значение не становится недействительным.

Кэшированное значение PullSubscribed a состоит из значения a список других значений, которые должны быть признаны недействительными, если это значение недействительно, и некоторые скучные вещи по управлению памятью.

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

Invalidator это количественно 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
          -- ...
          }

За пределами внутренних органов, если существует способ постоянно пробовать Behavior это будет связано с MonadFix (PullM t) экземпляр или взаимная рекурсия через фиксацию pull а также 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 среда, чтобы попробовать это, но я не думаю, что результаты будут хорошими.

Я экспериментировал с этим некоторое время и нашел обходной путь. Кажется, работает с последней версией рефлекса на сегодняшний день. Хитрость заключается в том, чтобы принудительно аннулировать кэшированное значение каждый раз, когда вы оцениваете данное 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 функция. В текущей версии рефлекса последняя всегда пуста, поэтому проблем не должно быть. Но я не уверен в toReconnect: из кода кажется, что если у него есть подписанные ключи, они могут сломаться, если не будут обработаны должным образом. Хотя я не уверен, может ли такое поведение иметь подписанные ключи или нет.

ОБНОВЛЕНИЕ для тех, кто действительно хочет реализовать это: приведенный выше код может зайти в тупик в некоторых сложных настройках. Моим решением было сделать аннулирование немного позже самого вычисления в отдельном потоке. Вот полный фрагмент кода. Решение по ссылке, похоже, работает правильно (используя его уже почти год в производстве).

Другие вопросы по тегам