haskell reactive-programming frp reactive-banana

haskell - reactive-banana: evento de disparo que contiene el valor más actualizado de un Comportamiento



reactive-programming frp (1)

Supongamos que tengo un desencadenante de evento que quiero hacer dos cosas cuando se dispara. Primero, quiero que actualice el valor de algún comportamiento . En segundo lugar, si se cumplen otras condiciones, quiero que se dispare otro evento send_off con el valor actualizado del comportamiento. Expresado en forma de código, supongo que tengo

trigger :: Event b trigger = ... updateFromTrigger :: b -> (a -> a) updateFromTrigger = ... conditionFromTrigger :: b -> Bool conditionFromTrigger = ... behavior :: Behavior a behavior = accumB initial_value (updateFromTrigger <$> trigger) send_off :: Event a send_off = ?????? (filterE conditionFromTrigger trigger)

Entonces la pregunta es: ¿qué pongo en el ?????? de modo que send_off envía el valor de comportamiento más actualizado, con lo que me refiero al valor que incluye la actualización del desencadenante que se le aplicó.

Desafortunadamente, si entiendo correctamente, la semántica de Comportamiento es tal que el valor actualizado no está disponible inmediatamente para mí, por lo que mi única opción aquí es esencialmente duplicar el trabajo y volver a calcular el valor actualizado del comportamiento para que pueda usarlo inmediatamente en otro evento, es decir, para rellenar el ?????? con algo como

send_off = flip updateFromTrigger <$> behavior <@> filterE conditionFromTrigger trigger

Ahora, hay un sentido en el que puedo hacer que la información actualizada sobre el comportamiento esté disponible para mí de inmediato utilizando un Discreto en lugar de un Comportamiento, pero en realidad esto es simplemente equivalente a un evento que se dispara simultáneamente con mi evento original. con el valor actualizado, y, a menos que haya omitido algo, reactive-banana no me da una forma de activar un evento solo cuando otros dos eventos se han activado simultáneamente; es decir, proporciona uniones de eventos pero no intersecciones.

Así que tengo dos preguntas. Primero, ¿es correcto entender esta situación y, en particular, tengo razón al concluir que mi solución anterior es la única forma de solucionarlo? En segundo lugar, simplemente por curiosidad, ¿ha habido algún pensamiento o plan por parte de los desarrolladores sobre cómo tratar las intersecciones de eventos?


Excelente pregunta!

Desafortunadamente, creo que hay un problema fundamental aquí que no tiene una solución fácil. El problema es el siguiente: desea el valor acumulado más reciente, pero el trigger puede contener eventos que ocurren simultáneamente (que aún están ordenados). Entonces,

¿Cuál de las actualizaciones simultáneas del acumulador será la más reciente?

El punto es que las actualizaciones se ordenan en la secuencia de eventos a la que pertenecen, pero no en relación con otras secuencias de eventos. La semántica de FRP utilizada aquí ya no sabe qué actualización simultánea del behavior corresponde a qué evento send_off simultáneo. En particular, esto muestra que su implementación propuesta para send_off es probablemente incorrecta; no funciona cuando el trigger contiene eventos simultáneos porque el comportamiento puede actualizarse varias veces, pero solo está recalculando la actualización una vez.

Con esto en mente, puedo pensar en varios enfoques para el problema:

  1. Utilice mapAccum para anotar cada evento de activación con el valor del acumulador recién actualizado.

    (trigger'', behavior) = mapAccum initial_value $ f <$> trigger where f x acc = (x, updateFromTrigger acc) send_off = fmap snd . filterE (conditionFromTrigger . fst) $ trigger''

    Creo que a esta solución le falta un poco en términos de modularidad, pero a la luz de la discusión anterior, es probable que esto sea difícil de evitar.

  2. Refundir todo en términos de Discrete .

    No tengo ninguna sugerencia concreta aquí, pero puede ser que su evento send_off se sienta más como una actualización de un valor que como un evento apropiado. En ese caso, puede valer la pena emitir todo en términos de Discrete , cuya instancia de Applicative hace "lo correcto" cuando ocurren eventos simultáneos.

    En un espíritu similar, a menudo utilizo changes . accumD changes . accumD lugar de accumE porque se siente más natural.

  3. La próxima versión de reactive-banana (> 0.4.3) probablemente incluirá funciones

    collect :: Event a -> Event [a] spread :: Event [a] -> Event a

    que reifican, resp. Reflejar eventos simultáneos. Los necesito para optimizar el tipo Discrete todos modos, pero probablemente también sean útiles para cosas como la pregunta actual.

    En particular, le permitirían definir la intersección de eventos de esta manera:

    intersect :: Event a -> Event b -> Event (a,b) intersect e1 e2 = spread . fmap f . collect $ (Left <$> e1) `union` (Right <$> e2) where f xs = zipWith (/(Left x) (Right y) -> (x,y)) left right where (left, right) = span isLeft xs

    Sin embargo, a la luz de la discusión anterior, esta función puede ser menos útil de lo que le gustaría que fuera. En particular, no es único, hay muchas variantes.