Después de leer muchas páginas sobre FRP, finalmente me encontré con este escrito esclarecedor sobre FRP, finalmente me hizo comprender de qué se trata realmente FRP.
Cito a continuación Heinrich Apfelmus (autor de plátano reactivo).
¿Cuál es la esencia de la programación reactiva funcional?
Una respuesta común sería que "FRP se trata de describir un sistema en términos de funciones que varían en el tiempo en lugar de un estado mutable", y eso ciertamente no estaría mal. Este es el punto de vista semántico. Pero en mi opinión, la respuesta más profunda y más satisfactoria es dada por el siguiente criterio puramente sintáctico:
La esencia de la programación reactiva funcional es especificar el comportamiento dinámico de un valor completamente al momento de la declaración.
Por ejemplo, tome el ejemplo de un contador: tiene dos botones etiquetados “Arriba” y “Abajo” que se pueden usar para aumentar o disminuir el contador. Imperativamente, primero debe especificar un valor inicial y luego cambiarlo cada vez que se presiona un botón; algo como esto:
counter := 0 -- initial value
on buttonUp = (counter := counter + 1) -- change it later
on buttonDown = (counter := counter - 1)
El punto es que en el momento de la declaración, solo se especifica el valor inicial para el contador; El comportamiento dinámico del contador está implícito en el resto del texto del programa. Por el contrario, la programación reactiva funcional especifica todo el comportamiento dinámico en el momento de la declaración, así:
counter :: Behavior Int
counter = accumulate ($) 0
(fmap (+1) eventUp
`union` fmap (subtract 1) eventDown)
Siempre que desee comprender la dinámica del contador, solo tiene que mirar su definición. Todo lo que le pueda pasar aparecerá en el lado derecho. Esto contrasta mucho con el enfoque imperativo en el que las declaraciones posteriores pueden cambiar el comportamiento dinámico de los valores declarados previamente.
Entonces, en mi opinión, un programa FRP es un conjunto de ecuaciones:
j
es discreto: 1,2,3,4 ...
f
depende de t
lo que esto incorpora la posibilidad de modelar estímulos externos
todo el estado del programa está encapsulado en variables x_i
La biblioteca de FRP se encarga de tiempo progresa, es decir, teniendo j
a j+1
.
Explico estas ecuaciones con mucho más detalle en este video.
EDITAR:
Aproximadamente 2 años después de la respuesta original, recientemente llegué a la conclusión de que las implementaciones de FRP tienen otro aspecto importante. Necesitan (y generalmente lo hacen) resolver un problema práctico importante: la invalidación de caché .
Las ecuaciones para x_i
-s describen un gráfico de dependencia. Cuando algunos de los x_i
cambios se realizan en el momento j
, no es necesario actualizar todos los demás x_i'
valores j+1
, por lo que no es necesario volver a calcular todas las dependencias, ya que algunas x_i'
podrían ser independientes x_i
.
Además, los x_i
-s que cambian pueden actualizarse gradualmente. Por ejemplo, consideremos una operación de mapa f=g.map(_+1)
en Scala, donde f
y g
son List
de Ints
. Aquí f
corresponde a x_i(t_j)
y g
es x_j(t_j)
. Ahora, si antepongo un elemento a g
, sería un desperdicio llevar a cabo la map
operación para todos los elementos en g
. Algunas implementaciones de FRP (por ejemplo, reflex-frp ) tienen como objetivo resolver este problema. Este problema también se conoce como computación incremental.
En otras palabras, los comportamientos (los x_i
-s) en FRP pueden considerarse como cálculos en caché. Es tarea del motor FRP invalidar y volver a calcular de manera eficiente estos caché (s x_i
) si algunos de los f_i
cambios cambian.