11

動作がdになるたびに IO アクションが実行されるという意図されたセマンティクスを使用して、 Behavior t afromを作成したいと思います。IO asample

{- 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継続的でいつでも観測可能な現象を作成する正しい方法は何ですか?

4

2 に答える 2

4

これを行うのSpiderは不可能に見えます。Internal先に推論します。

Spider実装でReflex可能なBehaviorの 1 つは、値をプルすることです。

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

Pulled 値は、必要に応じて値を計算する方法 と、pullCompute不要な再計算を避けるためにキャッシュされた値 で構成されますpullValue

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

の醜い環境を無視すると、計算が明らかな方法でBehaviorM持ち上げliftIOられ、をサンプリングする必要があるときに実行されます。では、動作は一度観察されますが、キャッシュされた値が無効化されていないため、再度観察されることはありません。IOBehaviorMPull

キャッシュされた値PullSubscribed aは、 value a、この値が無効化された場合に無効化する必要がある他の値のリスト、およびいくつかの退屈なメモリ管理要素で構成されます。

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

Anは、メモリ参照を取得して無効化ツールを再帰的に読み取って無効化し、キャッシュされた値を に書き込むのに十分なInvalidator数量化されたものです。PullNothing

常にプルするには、自分自身の を常に無効にできるようにしたいと考えてい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)pullsample

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

私はしばらくこれを試していて、回避策を見つけました。現在までの最新バージョンの 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関数の結果を無視します。現在のバージョンの reflex では、後者は常に空であるため、問題は発生しません。しかし、私はよくわかりませんtoReconnect:コードから、サブスクライブされたスイッチがいくつかある場合、適切に処理されないと壊れる可能性があるようです。この種の動作でスイッチをサブスクライブできるかどうかはわかりませんが。

本当にこれを実装したい人のための更新: 上記のコードは、いくつかの複雑なセットアップでデッドロックする可能性があります。私の解決策は、別のスレッドで計算自体の少し後に無効化を実行することでした。 完全なコード スニペットを次に示します。リンクによる解決策は正しく機能しているようです (現在、ほぼ 1 年間実稼働環境で使用しています)。

于 2017-07-20T09:31:19.897 に答える