2011-10-21 12 views
15

Tenemos un código como éste:¿Puede el asa reactiva-banana ciclos en la red?

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

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

Se puede ver que evtAutoLayout alimenta en guiState que se alimenta en evtAutoLayout - por lo que hay un ciclo allí. Esto es deliberado. El diseño Auto ajusta el estado de la GUI hasta que alcanza un equilibrio y luego devuelve Nada y por lo tanto, debe detener el ciclo. Un nuevo cambio de modelo puede iniciarlo de nuevo, por supuesto.

Cuando juntamos esto, sin embargo, nos encontramos con un bucle infinito en la llamada a la función de compilación . Incluso si autoLayout = Nothing, todavía resulta en un desbordamiento de la pila durante la compilación.

Si quito la llamada unión en guiState y quitar evtAutoLayout de la foto ...

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

que trabaja muy bien.

¿Alguna sugerencia?

Respuesta

15

La pregunta

es compatible la biblioteca reactiva-banana eventos definidos de forma recursiva?

tiene no solo una sino tres respuestas. Las respuestas cortas son: 1. generalmente no, 2. a veces sí, 3. con solución alternativa sí.

Aquí las respuestas largas.

  1. La semántica de reactiva-banana hacer no apoyar la definición de un Event directamente en términos de sí mismo.

    Esta fue una decisión que Conal Elliott hizo en su semántica de FRP original y he decidido mantenerla. Su principal beneficio es que la semántica siguen siendo muy simple, siempre se puede pensar en términos de

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

    que he proporcionado un módulo Reactive.Banana.Model que implementa casi exactamente este modelo, puede consultar su código fuente para cualquier pregunta relacionada con la semántica de plátano reactivo. En particular, puede usarlo para razonar sobre su ejemplo: un cálculo con papel de bolígrafo & o probándolo en GHCi (con algunos datos falsos) le dirá que el valor evtAutoLayout es igual a _|_, es decir, no está definido.

    Esto último puede sorprender, pero como usted lo escribió, el ejemplo no está definido: el estado de la GUI solo cambia si ocurre un evento evtAutoLayout, pero solo puede suceder si usted sabe si el estado de la GUI cambia, lo que a su vez , etc. Siempre debe romper el bucle de retroalimentación estranguladora al insertando un pequeño retraso. Desafortunadamente, el plátano reactivo actualmente no ofrece una forma de insertar pequeños retrasos, principalmente porque no sé cómo describir pequeños retrasos en términos del modelo [(Time,a)] de una manera que permite la recursión. (Pero vea la respuesta 3.)

  2. Es posible y se recomienda definir un Event en términos de un Behavior que se refiera nuevamente al evento. En otras palabras, la recursión está permitida siempre que pase por un Comportamiento.

    Un ejemplo sencillo sería

    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)] 
    

    Dada una secuencia de eventos, la función filterRising devuelve solamente los eventos que se mayor que el anteriormente devuelto. Esto se insinúa en el documentation for the stepper function.

    Sin embargo, este no es probablemente el tipo de recursión que desea.

  3. Aún así, es posible insertar pequeñas demoras en reactivo-banana, simplemente no es parte de la biblioteca central y por lo tanto no viene con semántica garantizada. Además, necesitas algo de apoyo de tu ciclo de eventos para hacer eso.

    Por ejemplo, puede usar un wxTimer para programar un evento que ocurrirá inmediatamente después de haber manejado el actual. El ejemplo Wave.hs demuestra el uso recursivo de un wxTimer con plátano reactivo. No sé muy bien qué sucede cuando configura el intervalo del temporizador en 0, aunque puede que se ejecute demasiado pronto. Probablemente tengas que experimentar un poco para encontrar una buena solución.

Espero que ayude; no dude en pedir aclaraciones, ejemplos, etc.

Divulgación: soy el autor de la biblioteca de plátano reactivo.

+0

Como dijo que podía pedir aclaraciones/ejemplos ... en su filtro, ¿para qué sirve el primer parámetro? Si solo está convirtiendo un Evento en un Evento, ¿por qué tiene 2 params? ¿Y cómo usarías filterRising? ¡Gracias! – mentics

+0

@taotree: Ah, el primer parámetro fue solo una especie de valor inicial. Ahora he cambiado el ejemplo para que coincida con la descripción. Usar la función 'filterRising' es simple: toma una secuencia de eventos como argumento y como resultado genera una nueva secuencia de eventos, por lo que puede aplicarla a una secuencia de eventos de su elección y obtener una nueva. –

Cuestiones relacionadas