éstos tilda sólo solo rae que pronombres palabras llevan está estos esta ejemplos diferencia demostrativos haskell functional-programming frp reactive-banana elm

haskell - tilda - FRP-Transmisión de eventos y señales: ¿qué se pierde con el uso de solo señales?



sólo rae (4)

En las implementaciones recientes de Classic FRP, por ejemplo, reactive-banana, hay flujos de eventos y señales, que son funciones de pasos (reactive-banana los llama comportamientos, pero sin embargo son funciones de pasos). Me he dado cuenta de que Elm solo usa señales y no distingue 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 usando reactimate '', aunque no se considera una buena práctica), lo que significa que en teoría podríamos aplicar todo el flujo de eventos. combinadores en señales / comportamientos convirtiendo primero la señal en un flujo de eventos, aplicando y luego convirtiendo nuevamente. 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 con el uso de solo señales y la conversión de todos los combinadores de flujo de eventos para operar con señales?

Edit: La discusión ha sido muy interesante. La principal conclusión que saqué de la discusión es que tanto los comportamientos como las fuentes de eventos son necesarios para definiciones recursivas mutuas (retroalimentación) y para tener una salida que depende de dos entradas (una conducta y una fuente de eventos) pero solo causa una acción de ellos cambia (<@>).


Algo de importancia crítica para mí se pierde, a saber, la esencia de los comportamientos, que es (posiblemente una variación continua) a lo largo del tiempo continuo. La semántica precisa, simple y útil (independientemente de una implementación o ejecución particular) a menudo también se pierde. Echa un vistazo a mi respuesta a "Especificación para un lenguaje de programación reactiva funcional", y sigue los enlaces que aparecen allí.

Ya sea en el tiempo o en el espacio, la discretización prematura frustra la capacidad de composición y complica la semántica. Considere los gráficos vectoriales (y otros modelos espacialmente continuos como el de Pan ). Al igual que con la finalización prematura de las estructuras de datos como se explica en Por qué importa la programación funcional .


Lamentablemente no tengo referencias en mente, pero recuerdo claramente diferentes autores reactivos que afirman que esta elección es solo por eficiencia. Expone ambos para que el programador pueda elegir qué implementación de la misma idea es más eficiente para su problema.

Puede que esté mintiendo ahora, pero creo que Elm implementa todo a medida que el evento se transmite bajo el capó. Sin embargo, cosas como el tiempo no serían tan agradables como las transmisiones de eventos, ya que hay una cantidad infinita de eventos en cualquier período de tiempo. No estoy seguro de cómo lo soluciona Elm, pero creo que es un buen ejemplo de algo que tiene más sentido como una señal, tanto conceptual como en la implementación.


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

Eficiencia de WRT, con una API de solo señales, el sistema debe tener un mecanismo donde solo las señales que tienen valores actualizados causarán recálputos (por ejemplo, si no mueve el mouse, la red de FRP no volverá a calcular las coordenadas del puntero y volverá a dibujar). la pantalla). Siempre que se haga esto, 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 de esta manera.

No creo que el problema del comportamiento continuo haga ninguna diferencia aquí (o en realidad). Lo que quiere decir la gente al decir que los comportamientos son continuos a lo largo del tiempo es que se definen 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 probar un comportamiento en ningún momento; solo se pueden muestrear en los momentos correspondientes a los eventos, por lo que no podemos utilizar todo el poder de esta definición.

Semánticamente, a partir de estas definiciones:

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

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

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

sin perder ningún poder (es decir, esto es equivalente a la definición original dentro de los límites de nuestro sistema frp). Ahora podemos enumerar todos los tiempos en TX para transformar esto a

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

que es idéntica a la definición original del Event excepto por el dominio y la cuantificación. Ahora podemos cambiar el dominio de Event a TX (según la definición de TX ), y la cuantificación de Behavior (de forall to for some) y obtenemos

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

y ahora el Event y el Behavior son semánticamente idénticos, por lo que, obviamente, podrían representarse utilizando la misma estructura en un sistema FRP. Perdemos un poco de información en este paso; Si no diferenciamos entre Event y Behavior , no sabemos que un Behavior se define en cada momento t , pero en la práctica no creo que esto realmente importe mucho. Lo que hace el olmo, es que IIRC requiere que tanto el Event como el Behavior tengan valores en todo momento y solo usen el valor anterior para un Event si no ha cambiado (es decir, cambiar la cuantificación del Event para que sea todo en lugar de cambiar la cuantificación del Behavior ) . Esto significa que puedes tratar todo como una señal y todo funciona simplemente; solo se implementa de modo que el dominio de señal sea exactamente el subconjunto de tiempo que el sistema realmente usa.

Creo que esta idea se presentó en un documento (que no puedo encontrar ahora, ¿alguien más tiene un enlace?) Sobre la implementación de FRP en Java, quizás de POPL ''14? Trabajando de memoria, 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 simplemente significa que dentro de un sistema FRP no puede usar esa definición adicional, por lo que una implementación más restringida no pierde nada.

En cuanto a las señales nocionales 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, convertirla en un flujo de eventos es trivial.

En resumen, no creo que se pierda nada con solo el uso de señales.


(Aclaración: en reactivo-banana, no es posible convertir un Behavior en un Event . La función stepper es un boleto de ida. Hay una función de changes , pero su tipo indica que es "impura" y que viene con una advertencia de que no conserva 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 API. Creo que los dos conceptos se comportan de manera lo suficientemente diferente como para que las cosas fluyan 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 combina ambos tipos en uno, entonces ninguna de estas equivalencias se mantendrá.

Sin embargo, una de las razones principales para la separación de Evento y Comportamiento es que este último 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 de GUI de entrada que muestra el texto indicado en el argumento Comportamiento,

input <- newInput (bText :: Behavior String)

El punto clave ahora es que el texto que se muestra no depende de la frecuencia con la que se haya actualizado el comportamiento bText (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 en el otro caso, en el que tendría que pensar en lo que sucede cuando dos eventos sucesivos tienen el mismo valor. ¿Redibujas 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 eso es el combinador de changes . Sin embargo, esto puede verse como una optimización y no está disponible desde "dentro de FRP".)

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

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

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