FRP-Event streams and Signals-¿qué se pierde al usar solo señales?


En implementaciones recientes de FRP Clásico, por ejemplo reactive-banana, hay flujos de eventos y señales, que son funciones escalonadas (reactive-banana las llama comportamientos, pero sin embargo son funciones escalonadas). He notado que Elm solo usa señales, y no diferencia entre señales y flujos de eventos. Además, reactive-banana permite pasar de flujos de eventos a señales (editado: y es posible actuar sobre comportamientos utilizando reactimate ' aunque no se considera bueno práctica), lo que significa que en teoría podríamos aplicar todos los combinadores de flujo de eventos en señales/comportamientos convirtiendo primero la señal en flujo de eventos, aplicándola y luego convirtiéndola de nuevo. Entonces, dado que en general es más fácil de usar y aprender solo una abstracción, ¿cuál es la ventaja de tener señales separadas y flujos de eventos ? ¿Se pierde algo al usar solo señales y convertir todos los combinadores de flujo de eventos para operar en señales ?

Editar: La discusión tiene ha sido muy interesante. Las principales conclusiones que saqué de la discusión es que las fuentes de comportamientos / eventos son necesarias para definiciones mutuamente recursivas (feedback) y para que una salida dependa de dos entradas (una fuente de comportamiento y una fuente de evento), pero solo causan una acción cuando una de ellas cambia ().

Author: miguel.negrao, 2014-04-10

4 answers

(Aclaración: reacción-plátano, no es posible convertir un Behavior de nuevo a un Event. La función stepper es un ticket de ida. Hay una función changes, pero su tipo indica que es "impura" y viene con una advertencia de que no preserva la semántica.)

Creo que tener dos conceptos separados hace que la API sea más elegante. En otras palabras, se reduce a una cuestión de usabilidad de la API. Creo que los dos conceptos se comportan de manera suficientemente diferente que las cosas fluyen mejor si tienes dos tipos separados.

Por ejemplo, el producto directo para cada tipo es diferente. Un par de Comportamiento es equivalente a un Comportamiento de pares

(Behavior a, Behavior b) ~ Behavior (a,b)

Mientras que un par de Eventos es equivalente a un Evento de una suma directa :

(Event    a, Event    b) ~ Event (EitherOrBoth a b)

Si fusiona ambos tipos en uno, entonces ninguna de estas equivalencias se mantendrá más.

Sin embargo, una de las principales razones para la separación de Evento y Comportamiento es que el éste no no tiene una noción de cambios o "actualizaciones". Esto puede parecer una omisión al principio, pero es extremadamente útil en la práctica, porque conduce a un código más simple. Por ejemplo, considere una función monádica newInput que crea un widget GUI de entrada que muestra el texto indicado en el comportamiento del argumento,

input <- newInput (bText :: Behavior String)

El punto clave ahora es que el texto mostrado no depende de con qué frecuencia el Comportamiento bText puede haber sido actualizado (para el mismo valor o un valor diferente), solo en el valor real en sí. Esto es mucho más fácil de razonar que el otro caso, donde tendría que pensar en lo que sucede cuando dos ocurrencias de eventos sucesivos tienen el mismo valor. ¿Redibuja el texto mientras el usuario lo edita?

(Por supuesto, para dibujar realmente el texto, la biblioteca tiene que interactuar con el marco de la GUI y hace un seguimiento de los cambios en el Comportamiento. Para esto es el combinador changes. Sin embargo, esto puede ser visto como una optimización y no está disponible desde "dentro de FRP".)

La otra razón principal para la separación es recursión. La mayoría de los eventos que dependen recursivamente de sí mismos están mal definidos. Sin embargo, la recursión es siempre permitida si tienes recursión mutua entre un Evento y un Comportamiento

e = ((+) <$> b) <@> einput
b = stepper 0 e

No hay necesidad de introducir retrasos a mano, solo funciona fuera de la caja.

 23
Author: Heinrich Apfelmus,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-04-12 09:19:37

Se pierde algo críticamente importante para mí, a saber, la esencia de los comportamientos, que es (posiblemente continua) la variación sobre el tiempo continuo. La semántica precisa, simple y útil (independiente de una implementación o ejecución en particular) también se pierde a menudo. Echa un vistazo a mi respuesta a "Especificación para un lenguaje de programación Reactivo Funcional", y sigue los enlaces allí.

Ya sea en el tiempo o en el espacio, la discretización prematura frustra la componibilidad y complica semántica. Considere los gráficos vectoriales (y otros modelos espacialmente continuos como Pan's). Al igual que con la finitización prematura de estructuras de datos como se explica en Por Qué Importa la Programación Funcional.

 14
Author: Conal,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 10:30:59

No creo que haya ningún beneficio al usar la abstracción de señales/comportamientos sobre las señales estilo elm. Como señala, es posible crear una API de solo señal encima de la API de señal/comportamiento (no está en absoluto lista para usar, pero consulte https://github.com/JohnLato/impulse/blob/dyn2/src/Reactive/Impulse/Syntax2.hs por ejemplo). Estoy bastante seguro de que también es posible escribir una API de señal/comportamiento sobre una API de estilo elm. Eso haría que las dos API funcionalmente equivalente.

WRT efficiency, with a signals-only API the system should have a mechanism where only signals that have updated values will cause recomputations (e.g. if you don't move the mouse, the FRP network won't re-calculate the pointer coordinates and redraw the screen). Siempre que esto se haga, no creo que haya ninguna pérdida de eficiencia en comparación con un enfoque de señales y flujos. Estoy bastante seguro de que Elm funciona así.

No creo que el problema del comportamiento continuo hace cualquier diferencia aquí (o realmente en absoluto). Lo que la gente quiere decir al decir que los comportamientos son continuos en el tiempo es que están definidos en todo momento( es decir, son funciones sobre un dominio continuo); el comportamiento en sí no es una función continua. Pero en realidad no tenemos una manera de muestrear un comportamiento en cualquier momento; solo se pueden muestrear en momentos que corresponden a eventos, ¡así que no podemos usar toda la potencia de esta definición!

Semánticamente, a partir de estos definiciones:

Event    == for some t ∈ T: [(t,a)]
Behavior == ∀ t ∈ T: t -> b

Dado que los comportamientos solo se pueden muestrear en momentos en los que se definen los eventos, podemos crear un nuevo dominio TX donde TX es el conjunto de todos los momentos t en los que se definen los Eventos. Ahora podemos aflojar la definición de Comportamiento a

Behavior == ∀ t ∈ TX: t -> b

Sin perder ninguna potencia (es decir, esto es equivalente a la definición original dentro de los confines de nuestro sistema frp). Ahora podemos enumerar todas las veces en TX para transformar esto en

Behavior == ∀ t ∈ TX: [(t,b)]

Que es idéntica a la definición original Event excepto el dominio y la cuantificación. Ahora podemos cambiar el dominio de Event a TX (por la definición de TX), y la cuantificación de Behavior (desde forall para algunos) y obtenemos

Event    == for some t ∈ TX: [(t,a)]
Behavior == for some t ∈ TX: [(t,b)]

Y ahora Event y Behavior son semánticamente idénticos, por lo que obviamente podrían representarse usando la misma estructura en un sistema FRP. Nos hacen perder un poco de información en este paso; si no diferenciamos entre Event y Behavior no sabemos que Behavior se define en cada tiempo t, pero en la práctica no creo que esto realmente importe mucho. Lo que elm hace IIRC es requerir tanto Events como Behaviors que tengan valores en todo momento y simplemente usar el valor anterior para un Event si no ha cambiado (es decir, cambiar la cuantificación de Event a forall en lugar de cambiar la cuantificación de Behavior). Esto significa que puede tratar todo como una señal y todo Funciona; solo se implementa para que el dominio de la señal sea exactamente el subconjunto de tiempo que el sistema realmente usa.

Creo que esta idea fue presentada en un documento (que no puedo encontrar ahora, alguien más tiene un enlace?) sobre la implementación de FRP en Java, tal vez desde POPL '14? Trabajando de memoria, así que mi esquema no es tan riguroso como la prueba original.

No hay nada que le impida crear un Behavior más definido por ejemplo pure someFunction, esto solo significa que dentro de un sistema FRP no puede hacer uso de esa definición extra, por lo que nada se pierde por un sistema más restringido aplicación.

En cuanto a las señales teóricas como el tiempo, tenga en cuenta que es imposible implementar una señal de tiempo continuo real utilizando lenguajes de programación típicos. Dado que la implementación será necesariamente discreta, convertir eso en una secuencia de eventos es trivial.

En resumen, no creo que nada se pierda usando solo señales.

 4
Author: John L,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-04-10 21:56:32

Desafortunadamente no tengo referencias en mente, pero recuerdo claramente diferentes autores reactivos afirman que esta elección es solo por eficiencia. Expone tanto para dar al programador una opción en lo que la implementación de la misma idea es más eficiente para su problema.

Podría estar mintiendo ahora, pero creo que Elm implementa todo como flujos de eventos bajo el capucha. Sin embargo, cosas como el tiempo no serían tan agradables como las transmisiones de eventos, ya que hay cantidad infinita de eventos durante cualquier marco de tiempo. No estoy seguro de cómo Elm resuelve esto, pero creo que es un buen ejemplo de algo que tiene más sentido como una señal, tanto conceptualmente y en aplicación.

 1
Author: monocell,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-04-10 18:36:16