Работа с текущим временем в реактивном банане

Как вы справляетесь с текущим временем в реактивном банане?

В идеале я хотел бы иметь Behaviour, который я могу «опросить», чтобы получить текущее время. Однако опрос Behaviours с Events (через <@ и т. д.) дает мне значение Behaviour из предыдущего Event, а не текущее значение. (Я понимаю, что это делается для того, чтобы избежать циклических определений, что действительно полезно.)

Я нашел fromPoll, который, как я думал, поможет. Behaviour, которые наблюдаются из fromPoll, не могут зависеть от самих себя, поэтому нельзя ввести циклы, наблюдая за поведением непосредственно перед запуском этого Event, а не сразу после запуска предыдущего Event.

Отступление

В несколько более формальных терминах я предполагаю, что Events всегда происходят во время t+, а Behaviours всегда наблюдаются во время t-, то есть Events наблюдают за поведением, которое происходит за бесконечно малое время до них. . Новые значения Behaviours, сгенерированные accumB и друзьями, всегда будут начинаться с момента времени t+, поэтому не могут наблюдаться Events, что также происходит в момент времени t+.

Согласно предложенной семантике Behaviour, созданные fromPoll, будут обновляться непосредственно перед обработкой каждого Event. Другие Behaviour будут обновлены позже, потому что они созданы accumB и друзьями.

Мой вариант использования

В любом случае, это значительное отступление от моего основного вопроса. Я хочу знать, есть ли способ справиться с текущим временем (а не временем предыдущего Event) в реактивном банане. Мой вариант использования, например, для отслеживания пингов, которые отправляют объекты, и если какой-либо из них не отправил пинг в определенный интервал времени, чтобы сигнализировать о событии предупреждения.

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

Каков правильный способ справиться с этим?


person Tom Ellis    schedule 10.06.2014    source источник


Ответы (1)


Учитывая ваш пример использования, я думаю, у вас все будет хорошо, если вы будете держаться подальше от fromPoll. Чтобы объяснить почему, необходимо сделать несколько пояснений. (Примечание: далее «поток» относится к Event t a, а «появление» — к одному из составляющих их срабатываний.)

Однако опрос поведения с событиями (через <@ и т. д.) дает мне значение поведения из предыдущего события, а не текущее значение.

Я полагаю, вы намекаете на подобные объяснения из документации для stepper:

Обратите внимание, что знак «меньше» в сравнении timex < time означает, что значение поведения изменяется «слегка после» возникновения события. Это позволяет использовать рекурсивные определения.

Однако эта задержка касается только потока, используемого для определения поведения (то есть того, который вы передаете stepper/accumB) и любых потоков, синхронизированных с ним. Например, предположим, что у вас есть два независимых потока, eTick и eTock, и следующий сетевой фрагмент:

eIncrement = (+1) <$ eTick
bCount = accumB 0 eIncrement
eCountTick = bCount <@ eTick

eCountTock = bCount <@ eTock

eIncrement и eCountTick синхронизированы с eTick, поэтому значение, наблюдаемое через eCountTick, является "старым" значением; то есть значение до синхронизированного обновления. Однако с точки зрения eCountTock все это не имеет значения. Наблюдатель, использующий eCountTock, не может говорить о задержке, и значение всегда является текущим.

Поведение, наблюдаемое с fromPoll, не может зависеть от себя, поэтому нельзя ввести циклы, наблюдая за поведением непосредственно перед срабатыванием этого события, а не сразу после срабатывания предыдущего события.

Нас интересуют только потоки, синхронизированные с тем, который обновляет поведение. Таким образом, поскольку наблюдаемые значения «непосредственно перед следующим событием» и «сразу после предыдущего события» сводятся к одному и тому же. fromPoll, однако, немного запутывает ситуацию. Он создает поведение, которое обновляется всякий раз, когда в сети событий происходит любое событие; а так обновления синхронизируются с объединением всех потоков. Не существует такой вещи, как поток, независимый от события fromPoll, и поэтому наблюдаемое значение будет зависеть от задержки, какой бы мы ее ни наблюдали. Таким образом, fromPoll не будет работать для часов, управляемых приложением, которые требуют отслеживания непрерывных изменений с некоторой точностью.

Во всем вышесказанном подразумевается, что reactive-banana не имеет встроенного понятия времени. В каждом потоке есть только «логические» линии времени, которые могут быть переплетены путем слияния потоков. Поэтому, если нам нужно текущее поведение во времени, лучше всего построить его из независимого потока. Вот демонстрация этого подхода, который будет давать свежие и своевременные результаты, насколько позволяет точность threadDelay:

{-# LANGUAGE RankNTypes #-}
module Main where

import Control.Concurrent
import Control.Monad
import Data.Time
import Reactive.Banana
import Reactive.Banana.Frameworks

main = do
    let netDesc :: forall t. Frameworks t => Moment t ()
        netDesc = do
            (eTime, fireTime) <- newEvent
            liftIO . forkIO . forever $
                threadDelay (50 * 1000) >> getCurrentTime >>= fireTime
            bTime <- flip stepper eTime <$> liftIO getCurrentTime
            (eTick, fireTick) <- newEvent
            liftIO . forkIO . forever $
                threadDelay (5000 * 1000) >> fireTick ()
            reactimate $ print <$> bTime <@ eTick
    network <- compile netDesc
    actuate network >> threadDelay (52000 * 1000) >> pause network

bTime обновляется через eTime каждые 0,05 с; это наблюдается через eTick, поток, независимый от eTime, с появлением каждые 5 секунд. Затем вы можете использовать eTick и полученные от него потоки для наблюдения и обновления ваших сущностей. В качестве альтернативы вы можете комбинировать bTime и поведение объекта в аппликативном стиле, чтобы получить, например. поведения для последних пингов, которые следует наблюдать с помощью eTick.

В вашем случае каноническое поведение времени выглядит разумным; он концептуально ясен и легко обобщается для нескольких тиков. В любом случае, другие подходы, с которыми вы можете поиграть, включают избавление от bTime и использование eTick в качестве потока текущего времени с низким разрешением (хотя это, кажется, ускоряет накопление threadDelay ошибок) и избавление от eTick с помощью changes на получить поток свежеобновленных значений из поведения (поскольку у него есть свои причуды и неприятности, как намекает документация).

person duplode    schedule 12.06.2014
comment
А, так у меня должен быть один Behaviour для текущего времени и совершенно отдельный Behaviour для тиков. Это имеет смысл, спасибо. Все еще кажется позорным, что галочка не может ввести текущее время, но, возможно, я просто не знаю тонкостей, которые вызывают FRP! - person Tom Ellis; 12.06.2014
comment
@TomEllis В моем примере есть два независимых Event, но только один Behavior, bTime. eTick — это просто инструмент для наблюдения за другими вещами; в модели предметной области с ним не связано наблюдаемое. Если вы хотите указать время с галочкой (что может быть достаточно хорошим решением в простых случаях), самый простой способ - вообще не использовать Behavior; например, удалив bTime и eTick и напрямую используя eTime (с подходящим периодом тика). - person duplode; 12.06.2014
comment
Ах да, на самом деле я хотел написать два отдельных Event, а не Behaviour. - person Tom Ellis; 12.06.2014