2011-10-21 18 views
15

Mamy kod tak:Czy w sieci można obsługiwać cykle obsługi reaktywnego banana?

guiState :: Discrete GuiState 
guiState = stepperD (GuiState []) $ 
    union (mkGuiState <$> changes model) evtAutoLayout 

evtAutoLayout :: Event GuiState 
evtAutoLayout = fmap fromJust . filterE isJust . fmap autoLayout $ changes guiState 

Widać, że evtAutoLayout zasila guiState która doprowadza do evtAutoLayout - więc jest tam cykl. To jest celowe. Układ Auto dostosowuje stan GUI, aż osiągnie równowagę, a następnie zwraca Nothing, a więc powinien zatrzymać pętlę. Nowa zmiana modelu może oczywiście ponownie ją uruchomić.

Po umieszczeniu tego razem uruchamiamy nieskończoną pętlę w wywołaniu funkcji kompilacji . Nawet jeśli autoLayout = Nothing, nadal powoduje przepełnienie stosu podczas kompilacji.

Gdybym usunąć wywołanie związkową w guiState i usunąć evtAutoLayout z zdjęcie ...

guiState :: Discrete GuiState 
guiState = stepperD (GuiState []) $ mkGuiState <$> changes model 

to działa dobrze.

Wszelkie sugestie?

Odpowiedz

15

Pytanie

Czy wsparciu biblioteki reaktywny-bananowy rekurencyjnie zdefiniowanych zdarzeń?

ma nie tylko jedną, ale trzy odpowiedzi. Krótkie odpowiedzi to: 1. ogólnie nie, 2. czasami tak, 3. z obejściem tak.

Oto długie odpowiedzi.

  1. Semantyka-reaktywnego banana zrobić nie obsługują definiując Event bezpośrednio pod względem siebie.

    Jest to decyzja podjęta przez Conala Elliotta w jego oryginalnej semantyki FRP i zdecydowałem się go trzymać. Jego główną zaletą jest to, że semantyka pozostaje bardzo prosta, zawsze można myśleć w kategoriach

    type Behavior a = Time -> a 
    type Event a = [(Time,a)] 
    

    mam pod warunkiem moduł Reactive.Banana.Model który implementuje niemal dokładnie tego modelu, można skonsultować swój kod źródłowy dla wszelkich pytań dotyczących semantyki reaktywnego banana. W szczególności można go użyć do uzasadnienia twojego przykładu: obliczenie za pomocą pióra & lub wypróbowanie go w GHCi (z pewnymi fałszywymi danymi) powie Ci, że wartość evtAutoLayout jest równa _|_, tj. Niezdefiniowana.

    To ostatnie może być zaskakujące, ale jak napisałeś, przykład jest rzeczywiście niezdefiniowany: stan GUI zmienia się tylko wtedy, gdy zdarzenie evtAutoLayout ma miejsce, ale może się zdarzyć tylko wtedy, gdy wiesz, czy zmienia się stan GUI, który z kolei itp. Zawsze musisz przerwać pętlę sprzężenia zwrotnego przez wstawiając małe opóźnienie. Niestety, reaktywny banan nie oferuje obecnie możliwości wstawiania małych opóźnień, głównie dlatego, że nie wiem jak opisać małe opóźnienia w zakresie modelu [(Time,a)] w sposób, który pozwala rekursji. (Ale patrz odpowiedź 3.)

  2. Możliwe jest i zachęcamy do zdefiniowania Event w odniesieniu do Behavior, która odnosi się do wydarzenia ponownie. Innymi słowy, rekursja jest dozwolona tak długo, jak długo będziesz postępować.

    Prostym przykładem byłoby

    import Reactive.Banana.Model 
    
    filterRising :: (FRP f, Ord a) => Event f a -> Event f a 
    filterRising eInput = eOutput 
        where 
        eOutput = filterApply (greater <$> behavior) eInput 
        behavior = stepper Nothing (Just <$> eOutput) 
    
        greater Nothing _ = True 
        greater (Just x) y = x < y 
    
    example :: [(Time,Int)] 
    example = interpretTime filterRising $ zip [1..] [2,1,5,4,8,9,7] 
    -- example = [(1.0, 2),(3.0, 5),(5.0, 8),(6.0, 9)] 
    

    Biorąc pod strumień zdarzeń, funkcja filterRising zwraca tylko te zdarzenia, które są większe niż wcześniej wrócił. Jest to wskazane w documentation for the stepper function.

    Jednak prawdopodobnie nie jest to rodzaj pożądanej rekurencji.

  3. Nadal możliwe jest wstawianie małych opóźnień w reaktywnym bananie, to po prostu nie jest częścią biblioteki rdzeniowej i dlatego nie ma żadnej gwarantowanej semantyki. Ponadto, aby to zrobić, potrzebujesz wsparcia ze swojej pętli zdarzeń.

    Można na przykład użyć wxTimer, aby zaplanować wydarzenie tuż po przejściu do bieżącego. Przykład Wave.hs demonstruje rekursywne użycie wxTimer z reaktywnym bananem. Nie bardzo wiem, co się stanie, gdy ustawi się interwał czasowy na 0, ale może on zostać wykonany zbyt wcześnie. Prawdopodobnie musisz trochę poeksperymentować, aby znaleźć dobre rozwiązanie.

Nadzieję, że pomaga; nie wahaj się prosić o wyjaśnienia, przykłady itp.

Ujawnienie: Jestem autorem biblioteki reaktywnych bananów.

+0

Odkąd powiedziałem, że mogę poprosić o wyjaśnienia/przykłady ... w twoim filterRising, po co jest pierwszy parametr? Jeśli to tylko konwersja zdarzenia do zdarzenia, dlaczego ma 2 parametry? A jak wykorzystałbyś filterRising? Dzięki! – mentics

+0

@taotree: Ah, pierwszy parametr był tylko pewnego rodzaju wartością początkową. Teraz zmieniłem przykład, aby pasował do opisu. Używanie funkcji 'filterRising' jest proste: pobiera strumień zdarzeń jako argument i zwraca w rezultacie nowy strumień zdarzeń, dzięki czemu można go zastosować do wybranego strumienia zdarzeń i uzyskać nowy. –