2016-03-12 21 views
10

anlegen würde ich jedes Mal laufen würde das Verhalten sample d ein Behavior t a von einem IO a, mit der beabsichtigten Semantik, die die IO Aktion erstellen möge ich konnte dies tun, indem Sie einfach die measurement in einer pull Ausführung:ein Verhalten für ein kontinuierlich messbares Phänomen

onDemand measure = return $ pull (liftIO measure) 

jedoch die resultierende Behavior ändert sich nie nach einem anfänglichen measure.

Die Abhilfe, die ich tun konnte, war eine Attrappe Behavior zu schaffen, die „oft genug“ ändert und dann eine gefälschte Abhängigkeit von der erstellen:

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 

Dies ist dann wie erwartet funktioniert; aber da Behavior s nur bei Bedarf abgefragt werden können, sollte dies nicht notwendig sein.

Was ist der richtige Weg, um ein Behavior für ein kontinuierliches, beobachtbares Phänomen zu erstellen?

+0

Ich weiß nicht, wie ich tun soll, was Sie wollen, aber ich werde beobachten, dass das, wonach Sie fragen, semantisch sehr seltsam ist - die "Bedeutung" eines solchen Verhaltens könnte sich ändern, je nachdem wie Beobachter mit dem Verhalten interagierten. Ich kenne Ihr FRP-Framework nicht genug, um es sicher zu wissen, aber ich würde erwarten, dass Verhaltensweisen unabhängig von der Beobachtung sein sollen - und die Tatsache, dass Beobachtung zur Laufzeit die Berechnung der "Bedeutung" verändern kann, ist nur ein Implementierungsdetail anstatt etwas, das dem Programmierer zur Verfügung gestellt werden soll. –

+0

Ist es etwas wie "Signal" in Elm, das du erreichen willst? – zakyggaps

Antwort

4

Doing in Spider sieht unmöglich aus. Internal Argumentation voraus.

In der Spider Implementierung von Reflex ist einer der möglichen Behavior s, um den Wert zu ziehen.

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

A Pull ed Wert besteht aus, wie der Wert zu berechnen, wenn nötig, pullCompute und ein zwischengespeicherten Wert unnötige erneute Berechnung, pullValue zu vermeiden.

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

die hässliche Umgebung von BehaviorM Ignorieren, liftIO hebt Benutzer IO Berechnung die offensichtliche Art und Weise, es läuft, wenn die BehaviorM Bedürfnisse abgetastet werden. In der Pull wird Ihr Verhalten einmal beobachtet, aber nicht erneut beobachtet, da der zwischengespeicherte Wert nicht ungültig ist.

Der zwischengespeicherte Wert PullSubscribed a besteht aus dem Wert a, eine Liste von anderen Werten, die ungültig gemacht werden müssen, wenn dieser Wert ungültig ist, und einige langweilige Speicherverwaltung.

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

Ein Invalidator ein quantifizierter Pull, die die Speicherreferenz zu bekommen genug rekursiv invalidators zu lesen, um den zwischengespeicherten Wert zu Nothing ungültig zu machen und zu schreiben.

Um ständig zu ziehen, möchten wir in der Lage sein, unsere BehaviorM ständig ungültig zu machen. Bei der Ausführung hat die an die BehaviorM übergebene Umgebung eine Kopie ihres eigenen Invalidators, der von den Abhängigkeiten von BehaviorM verwendet wird, um sie ungültig zu machen, wenn sie selbst ungültig werden.

Von der internen Umsetzung von readBehaviorTracked scheint es keine Möglichkeit zu sein, dass das eigene invalidator Verhalten (wi) jemals in der Liste der Teilnehmer am Ende, die für ungültig erklärt werden, wenn sie abgetastet wird (invsRef).

a <- liftIO $ runReaderT (unBehaviorM $ pullCompute p) $ Just (wi, parentsRef) 
    invsRef <- liftIO . newIORef . maybeToList =<< askInvalidator 
    -- ... 
    let subscribed = PullSubscribed 
      { pullSubscribedValue = a 
      , pullSubscribedInvalidators = invsRef 
      -- ... 
      } 

Außerhalb der Einbauten, wenn es eine Möglichkeit existiert ständig eine Probe Behavior es würde eine MonadFix (PullM t) Instanz oder gegenseitige Rekursion durch Fixieren von pull und 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 

mir nicht habe eine Reflex Umgebung, um dies zu versuchen, aber ich denke nicht, dass die Ergebnisse schön sein werden.

+0

Hat 'reflex-dom' eine eigene' Reflex' -Instanz, oder verwendet sie darunter den gleichen 'Spider'-Typ? Ich wäre glücklich mit einer "reflex-dom" -Lösung. – Cactus

+1

FWIW, Ihr Vorschlag von 'fix $ \ timestamp -> pull $ sample timestamp >> liftIO read' verursacht wie erwartet eine Divergenz/Endlosschleife. – Cactus

2

Ich experimentierte schon eine Weile damit und fand einen Workaround. Es scheint mit der neuesten Version von Reflex bis heute zu funktionieren. Der Trick besteht darin, den zwischengespeicherten Wert bei jeder Auswertung einer gegebenen IO Aktion zwangsweise ungültig zu machen.

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 

Es ist wichtig, unsafeInterleaveIO zu verwenden, um den invalidator so spät wie möglich zu laufen, so dass es eine bestehende Sache entkräftet.

Es gibt ein anderes Problem mit diesem Code: Ich ignoriere toReconnect Referenz und das Ergebnis der invalidate Funktion. In der aktuellen Version von Reflex ist letzterer immer leer und sollte daher keine Probleme verursachen. Aber ich bin nicht sicher über toReconnect: aus dem Code scheint es, dass wenn es einige abonnierte Schalter hat, könnten sie brechen, wenn nicht richtig behandelt. Obwohl ich nicht sicher bin, ob diese Art von Verhalten Schalter abonniert haben kann oder nicht.