Как реализовать поведение реактивного банана, которое рекурсивно зависит от самого себя?

У меня есть поведение, значение которого я хочу изменить в зависимости от возникновения события и текущего значения поведения. В приведенном ниже примере у меня есть два счетчика, которые обновляются в зависимости от того, является ли логическое поведение истинным или ложным. Так как этот код дает сбой с исключением <<loop>>, но я не уверен, как реструктурировать его для работы или как еще решить эту проблему.

{-# LANGUAGE ScopedTypeVariables #-}

import Reactive.Banana
import Reactive.Banana.Frameworks

import Control.Arrow
import Control.Concurrent
import Control.Monad
import Control.Monad.Fix

counter :: Bool -> Event t Int -> Behavior t Bool -> (Behavior t Int, Event t (Bool -> Bool))
counter b input active = (result, whenE ((b/=) <$> active) (fmap (const not) input))
    where result = accumB 0 (fmap (+) evt')
          evt' = whenE ((b==) <$> active) input

alternater :: Event t Int -> Behavior t Bool -> (Behavior t (Bool, (Int, Int)), Event t (Bool -> Bool))
alternater input active = ((,) <$> active <*> ((,) <$> fst t1 <*> fst t2), snd t1 `union` snd t2)
    where t1 = counter True input active
          t2 = counter False input active

main :: IO ()
main = do
    (inputHandler, fireInput) <- newAddHandler
    let network :: forall t . Frameworks t => Moment t ()
        network = do
            eInput <- fromAddHandler inputHandler
            let ui :: Behavior t (Bool, (Int, Int)) -> Moment t (Behavior t (Bool, (Int, Int)))
                ui b = do
                    let (behavior, evt) = alternater eInput (fst <$> b)
                    return $ stepper id (fmap (***id) evt) <*> behavior
            output <- changes =<< mfix ui
            reactimate $ putStrLn . show <$> output
    forkIO $ actuate =<< compile network
    forever $ getLine >>= fireInput . read

person merijn    schedule 13.07.2013    source источник
comment
Вы используете accumB для вычисления result, поэтому я думаю, что behavior (внутри ui), вероятно, подойдет. Я подозреваю, что проблема в evt. evt также зависит от b, но для его вычисления не используются отсроченные или наблюдаемые примитивы. К сожалению, на данный момент у меня нет ни малейшего представления о том, что вы должны сделать, чтобы это исправить.   -  person Jason Dagit    schedule 14.07.2013
comment
На самом деле, похоже, что behavior находится внутри ui, вот в чем проблема, потому что, когда я удаляю ссылку на evt (превращая ее в return $ stepper id (fmap (***id) never) <*> behavior), я все равно сталкиваюсь с ‹‹loop››.   -  person merijn    schedule 14.07.2013
comment
Хм... в таком случае это может быть ошибка? В документах говорится, что accumB должен быть наблюдаемым. То же самое с stepper.   -  person Jason Dagit    schedule 14.07.2013


Ответы (1)


Исключение правильное, вы определяете поведение непосредственно с точки зрения самого себя.

alternater .. active = (.. <$> active <*> .. , ..)
ui b = do
    let (behavior, ..) = alternater .. (.. <$> b)
    return $ .. <*> behavior

... mfix ui

Этот код означает, что текущее значение поведения результата в ui будет циклически зависеть от самого себя.

Рекурсия всегда нуждается в небольшой задержке, чтобы быть четко определенной. Самый удобный способ сделать это — использовать взаимную рекурсию между событием и поведением, построенным с помощью stepper или accumB. См. также этот ответ.

person Heinrich Apfelmus    schedule 14.07.2013
comment
Вы правы, я попытался упростить исходный пример и не заметил, что поведение Bool нигде не имеет начальной спецификации. Однако, если я изменю определение с ui на ``let delay = stepper True $ (fst ‹$› b) ‹@ eInput`` и let (behavior, evt) = alternater eInput delay, то я все равно получу цикл, хотя все начальные значения кажутся указанными ( счетчик использует accumB, а задержка является степпером), я что-то упустил? - person merijn; 14.07.2013