¿Qué es la programación reactiva (funcional)?

1148

He leído el artículo de Wikipedia sobre programación reactiva . También leí el pequeño artículo sobre programación reactiva funcional . Las descripciones son bastante abstractas.

  1. ¿Qué significa la programación funcional reactiva (FRP) en la práctica?
  2. ¿En qué consiste la programación reactiva (en oposición a la programación no reactiva)?

Mi experiencia es en idiomas imperativos / OO, por lo que se agradecería una explicación que se relacione con este paradigma.

JtR
fuente
159
Aquí hay un tipo con una imaginación activa y buenas habilidades para contar historias que se encargan de todo. paulstovell.com/reactive-programming
melaos
39
Alguien realmente necesita escribir una "Programación funcional reactiva para tontos" para todos los autodidactas aquí. Todos los recursos que he encontrado, incluso Elm, parecen asumir que obtuviste una maestría en CS en los últimos cinco años. Los conocedores de FRP parecen haber perdido por completo la capacidad de ver el asunto desde el punto de vista ingenuo, algo crítico para la enseñanza, la capacitación y la evangelización.
TechZen
26
Otra excelente introducción de FRP: la introducción a la Programación Reactiva que mi colega André ha
echado de menos
55
Uno de los mejores que he visto, basado en ejemplos: gist.github.com/staltz/868e7e9bc2a7b8c1f754
Razmig
2
La analogía de la hoja de cálculo me parece muy útil como primera impresión aproximada (vea la respuesta de Bob: stackoverflow.com/a/1033066/1593924 ). Una celda de hoja de cálculo reacciona a los cambios en otras celdas (pulls) pero no se extiende y cambia otras (no empuja). El resultado final es que puede cambiar una celda y un millón de otras 'independientemente' actualizar sus propias pantallas.
Jon Coombs

Respuestas:

931

Si desea familiarizarse con FRP, puede comenzar con el antiguo tutorial de Fran de 1998, que tiene ilustraciones animadas. Para los artículos, comience con la animación funcional reactiva y luego siga los enlaces en el enlace de publicaciones en mi página de inicio y el enlace FRP en el wiki de Haskell .

Personalmente, me gusta pensar en lo que significa FRP antes de abordar cómo podría implementarse. (El código sin una especificación es una respuesta sin una pregunta y, por lo tanto, "ni siquiera está mal"). Por lo tanto, no describo FRP en términos de representación / implementación como lo hace Thomas K en otra respuesta (gráficos, nodos, bordes, disparos, ejecución, etc) Hay muchos estilos de implementación posibles, pero ninguna implementación dice qué es FRP .

Resueno con la simple descripción de Laurence G de que FRP se trata de "tipos de datos que representan un valor 'en el tiempo'". La programación imperativa convencional captura estos valores dinámicos solo indirectamente, a través del estado y las mutaciones. La historia completa (pasado, presente, futuro) no tiene una representación de primera clase. Además, solo los valores que evolucionan discretamente pueden capturarse (indirectamente), ya que el paradigma imperativo es temporalmente discreto. En contraste, FRP captura estos valores en evolución directamente y no tiene dificultades con los valores en evolución continua .

El FRP también es inusual en el sentido de que es concurrente sin chocar con el nido de ratas teórico y pragmático que plaga la concurrencia imperativa. Semánticamente, la concurrencia de FRP es fina , determinada y continua . (Estoy hablando de significado, no de implementación. Una implementación puede o no implicar concurrencia o paralelismo). La determinación semántica es muy importante para el razonamiento, tanto riguroso como informal. Si bien la simultaneidad agrega una enorme complejidad a la programación imperativa (debido al entrelazado no determinista), es sin esfuerzo en FRP.

Entonces, ¿qué es FRP? Podrías haberlo inventado tú mismo. Comience con estas ideas:

  • Los valores dinámicos / en evolución (es decir, valores "a lo largo del tiempo") son valores de primera clase en sí mismos. Puede definirlos y combinarlos, pasarlos dentro y fuera de las funciones. Llamé a estas cosas "comportamientos".

  • Los comportamientos se forman a partir de unas pocas primitivas, como los comportamientos constantes (estáticos) y el tiempo (como un reloj), y luego con una combinación secuencial y paralela. n los comportamientos se combinan aplicando una función n-aria (en valores estáticos), "puntual", es decir, continuamente a lo largo del tiempo.

  • Para tener en cuenta los fenómenos discretos, tenga otro tipo (familia) de "eventos", cada uno de los cuales tiene una secuencia (finita o infinita) de ocurrencias. Cada ocurrencia tiene un tiempo y un valor asociados.

  • Para llegar al vocabulario compositivo a partir del cual se pueden construir todos los comportamientos y eventos, juegue con algunos ejemplos. Sigue deconstruyendo en piezas que sean más generales / simples.

  • Para que sepas que estás en terreno sólido, dale a todo el modelo una base compositiva, utilizando la técnica de semántica denotacional, lo que solo significa que (a) cada tipo tiene un tipo matemático de "significados" simple y preciso correspondiente, y ( b) cada primitivo y operador tiene un significado simple y preciso en función de los significados de los constituyentes. Nunca, nunca mezcle consideraciones de implementación en su proceso de exploración. Si esta descripción es un truco para usted, consulte (a) Diseño de denominación con morfismos de clase de tipo , (b) Programación reactiva funcional Push-pull (ignorando los bits de implementación), y (c) la página de wikilibros de Haskell de la semántica de denominación. Tenga en cuenta que la semántica denotacional tiene dos partes, de sus dos fundadores Christopher Strachey y Dana Scott: la parte de Strachey más fácil y más útil y la parte de Scott más difícil y menos útil (para el diseño de software).

Si sigue estos principios, espero que obtenga algo más o menos en el espíritu de FRP.

¿De dónde saqué estos principios? En el diseño de software, siempre hago la misma pregunta: "¿qué significa?". La semántica de denominación me dio un marco preciso para esta pregunta, y uno que se ajusta a mi estética (a diferencia de la semántica operativa o axiomática, las cuales me dejan insatisfecho). ¿Entonces me pregunté qué es el comportamiento? Pronto me di cuenta de que la naturaleza temporalmente discreta de la computación imperativa es una adaptación a un estilo particular de máquina , más que una descripción natural del comportamiento en sí. La descripción más simple y precisa del comportamiento que se me ocurre es simplemente "función del tiempo (continuo)", así que ese es mi modelo. Deliciosamente, este modelo maneja concurrencia continua y determinista con facilidad y gracia.

Implementar este modelo de manera correcta y eficiente ha sido todo un desafío, pero esa es otra historia.

Conal
fuente
78
He estado al tanto de la programación reactiva funcional. Parece relacionado con mi propia investigación (en gráficos estadísticos interactivos) y estoy seguro de que muchas de las ideas serían útiles para mi trabajo. Sin embargo, me resulta muy difícil superar el lenguaje. ¿Realmente debo aprender acerca de la "semántica denotacional" y los "morfismos de clase tipo" para entender lo que está sucediendo? Una introducción general del público al tema sería muy útil.
hadley
212
@Conal: claramente sabes de lo que estás hablando, pero tu lenguaje supone que tengo un doctorado en matemáticas computacionales, lo cual no es cierto. Tengo experiencia en ingeniería de sistemas y más de 20 años de experiencia con computadoras y lenguajes de programación, pero siento que su respuesta me deja desconcertado. Te desafío a volver a publicar tu respuesta en inglés ;-)
mindplay.dk
50
@ minplay.dk: Tus comentarios no me dan mucho para continuar sobre lo que en particular no entiendes, y no estoy dispuesto a hacer conjeturas sobre qué subconjunto particular de inglés estás buscando. Sin embargo, los invito a decir específicamente qué aspectos de mi explicación anterior están tropezando, para que yo y otros podamos ayudarlos. Por ejemplo, ¿hay palabras particulares que le gustaría definir o conceptos para los que le gustaría agregar referencias? Realmente me gusta mejorar la claridad y la accesibilidad de mi escritura, sin disminuir el tono.
Conal
27
"Determinación" / "determinado" significa que hay un único valor correcto bien definido. Por el contrario, casi todas las formas de concurrencia imperativa pueden dar diferentes respuestas, dependiendo de un programador o si está buscando o no, e incluso pueden llegar a un punto muerto. "Semántico" (y más específicamente "denotacional") se refiere al valor ("denotación") de una expresión o representación, en contraste con "operacional" (cómo se calcula la respuesta o cuánto espacio y / o tiempo se consume por lo que tipo de máquina).
Conal
18
Estoy de acuerdo con @ mindplay.dk, aunque no puedo presumir de haber estado en el campo durante mucho tiempo. Aunque parecía que sabía de lo que estaba hablando, no me dio una comprensión rápida, breve y simple de lo que es esto, ya que estoy lo suficientemente mimado como para esperar en SO. Esta respuesta me condujo principalmente a un montón de preguntas nuevas sin responder realmente la primera. Espero que compartir la experiencia de seguir siendo relativamente ignorante en el campo pueda darle una idea de lo simple y breve que realmente necesita ser. Vengo de un fondo similar al OP, por cierto.
Aske B.
739

En la programación funcional pura, no hay efectos secundarios. Para muchos tipos de software (por ejemplo, cualquier cosa con interacción del usuario), los efectos secundarios son necesarios en algún nivel.

Una forma de obtener un comportamiento similar a los efectos secundarios mientras se conserva un estilo funcional es utilizar la programación funcional reactiva. Esta es la combinación de programación funcional y programación reactiva. (El artículo de Wikipedia al que se vinculó es sobre este último).

La idea básica detrás de la programación reactiva es que hay ciertos tipos de datos que representan un valor "con el tiempo". Los cálculos que involucran estos valores cambiantes a lo largo del tiempo tendrán valores que cambiarán con el tiempo.

Por ejemplo, podría representar las coordenadas del mouse como un par de valores enteros en el tiempo. Digamos que teníamos algo así (esto es un pseudocódigo):

x = <mouse-x>;
y = <mouse-y>;

En cualquier momento, x e y tendrían las coordenadas del mouse. A diferencia de la programación no reactiva, solo necesitamos hacer esta asignación una vez, y las variables x e y se mantendrán "actualizadas" automáticamente. Esta es la razón por la cual la programación reactiva y la programación funcional funcionan tan bien juntas: la programación reactiva elimina la necesidad de mutar variables y al mismo tiempo le permite hacer mucho de lo que podría lograr con mutaciones variables.

Si luego hacemos algunos cálculos basados ​​en esto, los valores resultantes también serán valores que cambian con el tiempo. Por ejemplo:

minX = x - 16;
minY = y - 16;
maxX = x + 16;
maxY = y + 16;

En este ejemplo, minXsiempre será 16 menos que la coordenada x del puntero del mouse. Con las bibliotecas reactivas, puede decir algo como:

rectangle(minX, minY, maxX, maxY)

Y se dibujará un cuadro de 32x32 alrededor del puntero del mouse y lo rastreará donde sea que se mueva.

Aquí hay un documento bastante bueno sobre programación reactiva funcional .

Laurence Gonsalves
fuente
25
Entonces, ¿la programación reactiva es una forma de programación declarativa?
troelskn
31
> Entonces, ¿la programación reactiva es una forma de programación declarativa? La programación reactiva funcional es una forma de programación funcional, que es una forma de programación declarativa.
Conal
77
@ user712092 No realmente, no. Por ejemplo, si llamo sqrt(x)a C con su macro, eso solo calcula sqrt(mouse_x())y me devuelve un doble. En un verdadero sistema reactivo funcional, sqrt(x)devolvería un nuevo "doble en el tiempo". Si intentara simular un sistema FR con #defineusted, tendría que renunciar a las variables a favor de las macros. Los sistemas FR también suelen recalcular las cosas cuando es necesario volver a calcularlas, mientras que el uso de macros significaría que estarías reevaluando constantemente todo, hasta las subexpresiones.
Laurence Gonsalves
44
"Para muchos tipos de software (por ejemplo, cualquier cosa con interacción del usuario), los efectos secundarios son necesarios en algún nivel". Y tal vez solo a nivel de implementación. Hay muchos efectos secundarios en la implementación de la programación funcional pura y perezosa, y uno de los éxitos del paradigma es mantener muchos de esos efectos fuera del modelo de programación. Mis propias incursiones en interfaces de usuario funcionales sugieren que también se pueden programar completamente sin efectos secundarios.
Conal
44
@tieTYT x nunca se reasigna / muta. El valor de x es la secuencia de valores a lo largo del tiempo. Otra forma de verlo es que, en lugar de que x tenga un valor "normal", como un número, el valor de x es (conceptualmente) una función que toma tiempo como parámetro. (Esto es una simplificación excesiva. No puede crear valores de tiempo que le permitan predecir el futuro de cosas como la posición del mouse.)
Laurence Gonsalves
144

Una manera fácil de llegar a una primera intuición sobre cómo es es imaginar que su programa es una hoja de cálculo y todas sus variables son celdas. Si alguna de las celdas de una hoja de cálculo cambia, las celdas que se refieren a esa celda también cambian. Es lo mismo con FRP. Ahora imagine que algunas de las celdas cambian por sí solas (o más bien, se toman del mundo exterior): en una situación de GUI, la posición del mouse sería un buen ejemplo.

Eso necesariamente se pierde bastante. La metáfora se descompone bastante rápido cuando realmente usa un sistema FRP. Por un lado, generalmente también hay intentos de modelar eventos discretos (por ejemplo, al hacer clic con el mouse). Solo pongo esto aquí para darte una idea de cómo es.


fuente
3
Un ejemplo extremadamente apropiado. Es genial tener cosas teóricas, y tal vez algunas personas tengan las implicaciones de eso sin recurrir a un ejemplo básico, pero necesito comenzar con lo que hace por mí, no lo que es abstractamente. Lo que obtuve recientemente (de las conversaciones de Rx de Netflix) es que RP (o Rx, de todos modos), hace que estos "valores cambiantes" sean de primera clase y te permite razonar sobre ellos, o escribir funciones que hacen cosas con ellos. Escriba funciones para crear hojas de cálculo o celdas, si lo desea. Y maneja cuando un valor termina (desaparece) y le permite limpiar automáticamente.
Benjohn
Este ejemplo enfatiza la diferencia entre la programación controlada por eventos y el enfoque reactivo, donde simplemente declara las dependencias para usar el enrutamiento inteligente.
kinjelom
131

Para mí se trata de 2 significados diferentes de símbolo =:

  1. En matemáticas x = sin(t)significa, ese xes un nombre diferente para sin(t). Entonces escribir x + yes lo mismo que sin(t) + y. La programación reactiva funcional es como las matemáticas a este respecto: si escribes x + y, se calcula con el valor que tsea ​​en el momento en que se usa.
  2. En los lenguajes de programación tipo C (lenguajes imperativos), x = sin(t)es una asignación: significa que xalmacena el valor de sin(t) tomado en el momento de la asignación.
usuario712092
fuente
55
Buena explicación. Creo que también podría agregar que "tiempo" en el sentido de FRP es normalmente "cualquier cambio de entrada externa". Cada vez que una fuerza externa cambia una entrada de FRP, mueve el "tiempo" hacia adelante y vuelve a calcular todo lo que se ve afectado por el cambio.
Didier A.
44
En matemática, x = sin(t)medios xes el valor de sin(t)lo dado t. Es no un nombre diferente para sin(t)como la función. De lo contrario lo sería x(t) = sin(t).
Dmitri Zaitsev
+ El signo Dmitri Zaitsev Equals tiene varios significados en matemáticas. Una de ellas es que siempre que veas el lado izquierdo, puedes cambiarlo por el lado derecho. Por ejemplo 2 + 3 = 5o a**2 + b**2 = c**2.
user712092
71

De acuerdo con los conocimientos previos y al leer la página de Wikipedia a la que apuntaste, parece que la programación reactiva es algo así como la computación de flujo de datos pero con "estímulos" externos específicos que activan un conjunto de nodos para disparar y realizar sus cálculos.

Esto es bastante adecuado para el diseño de la interfaz de usuario, por ejemplo, en el que tocar un control de interfaz de usuario (por ejemplo, el control de volumen en una aplicación de reproducción de música) podría necesitar actualizar varios elementos de visualización y el volumen real de salida de audio. Cuando modifica el volumen (un control deslizante, digamos) que correspondería a modificar el valor asociado con un nodo en un gráfico dirigido.

Varios nodos que tienen bordes de ese nodo de "valor de volumen" se activarán automáticamente y cualquier cálculo y actualización necesarios se moverán naturalmente por la aplicación. La aplicación "reacciona" al estímulo del usuario. La programación reactiva funcional sería simplemente la implementación de esta idea en un lenguaje funcional, o generalmente dentro de un paradigma de programación funcional.

Para obtener más información sobre "informática de flujo de datos", busque esas dos palabras en Wikipedia o utilice su motor de búsqueda favorito. La idea general es esta: el programa es un gráfico dirigido de nodos, cada uno de los cuales realiza un cálculo simple. Estos nodos están conectados entre sí mediante enlaces de gráficos que proporcionan las salidas de algunos nodos a las entradas de otros.

Cuando un nodo dispara o realiza su cálculo, los nodos conectados a sus salidas tienen sus entradas correspondientes "activadas" o "marcadas". Cualquier nodo que tenga todas las entradas activadas / marcadas / disponibles se dispara automáticamente. El gráfico puede ser implícito o explícito dependiendo exactamente de cómo se implementa la programación reactiva.

Los nodos pueden considerarse como disparos en paralelo, pero a menudo se ejecutan en serie o con un paralelismo limitado (por ejemplo, puede haber algunos subprocesos ejecutándolos). Un ejemplo famoso fue el Manchester Dataflow Machine , que (IIRC) utilizó una arquitectura de datos etiquetados para programar la ejecución de nodos en el gráfico a través de una o más unidades de ejecución. La computación de flujo de datos se adapta bastante bien a situaciones en las que disparar cálculos de forma asíncrona que da lugar a cascadas de cómputos funciona mejor que intentar que la ejecución se rija por un reloj (o relojes).

La programación reactiva importa esta idea de "cascada de ejecución" y parece pensar en el programa de una manera similar al flujo de datos, pero con la condición de que algunos de los nodos estén enganchados al "mundo exterior" y las cascadas de ejecución se activen cuando estos sensores -como los nodos cambian. La ejecución del programa se vería como algo análogo a un arco reflejo complejo. El programa puede o no ser básicamente sésil entre estímulos o puede establecerse en un estado básicamente sésil entre estímulos.

La programación "no reactiva" sería la programación con una visión muy diferente del flujo de ejecución y la relación con las entradas externas. Es probable que sea algo subjetivo, ya que las personas se verán tentadas a decir algo que responda a las entradas externas que "reaccionen" a ellas. Pero mirando el espíritu de la cosa, un programa que sondea una cola de eventos en un intervalo fijo y distribuye cualquier evento encontrado a funciones (o hilos) es menos reactivo (porque solo atiende la entrada del usuario en un intervalo fijo). Una vez más, aquí está el espíritu de la cosa: uno puede imaginar poner una implementación de sondeo con un intervalo de sondeo rápido en un sistema a un nivel muy bajo y programar de manera reactiva.

Thomas Kammeyer
fuente
1
OK, ahora hay algunas buenas respuestas arriba. ¿Debo eliminar mi publicación? Si veo que dos o tres personas dicen que no agrega nada, lo eliminaré a menos que aumente su recuento útil. No tiene sentido dejarlo aquí a menos que agregue algo de valor.
Thomas Kammeyer
3
ha mencionado el flujo de datos, por lo que agrega algo de valor en mi humilde opinión.
Rainer Joswig
Eso es lo que QML está destinado a ser, parece;)
mlvljr
3
Para mí, esta respuesta fue la más fácil de entender, especialmente porque el uso de análogos naturales como "ondulación a través de la aplicación" y "nodos sensoriales". ¡Excelente!
Akseli Palén
1
desafortunadamente, el enlace de Manchester Dataflow Machine está muerto.
Pac0
65

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: ingrese la descripción de la imagen aquí

j es discreto: 1,2,3,4 ...

fdepende de tlo 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 ja 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_icambios 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 fy gson Listde Ints. Aquí fcorresponde a x_i(t_j)y ges x_j(t_j). Ahora, si antepongo un elemento a g, sería un desperdicio llevar a cabo la mapoperació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_icambios cambian.

jhegedus
fuente
44
Estuve allí contigo hasta que fuiste con ecuaciones discretas . La idea fundacional de FRP fue el tiempo continuo , donde no hay " j+1". En cambio, piense en las funciones del tiempo continuo. Como Newton, Leibniz y otros nos mostraron, a menudo es muy útil (y "natural" en un sentido literal) describir estas funciones de manera diferencial, pero continua, utilizando integrales y sistemas de EDO. De lo contrario, estás describiendo un algoritmo de aproximación (y uno pobre) en lugar de la cosa misma.
Conal
La plantilla HTML y las restricciones de diseño del lenguaje layx parecen expresar elementos de FRP.
@Conal esto me hace preguntarme en qué se diferencia FRP de las EDO. ¿Cómo se diferencian?
jhegedus
@jhegedus En esa integración (posiblemente recursiva, es decir, ODE) proporciona uno de los componentes básicos de FRP, no la totalidad. Cada elemento del vocabulario de FRP (incluidos, entre otros, la integración) se explica con precisión en términos de tiempo continuo. ¿Ayuda esa explicación?
Conal
29

Descargo de responsabilidad: mi respuesta está en el contexto de rx.js, una biblioteca de 'programación reactiva' para Javascript.

En la programación funcional, en lugar de recorrer cada elemento de una colección, aplica funciones de orden superior (HoFs) a la colección misma. Entonces, la idea detrás de FRP es que, en lugar de procesar cada evento individual, cree una secuencia de eventos (implementada con un * observable) y aplique HoFs a eso en su lugar. De esta manera, puede visualizar el sistema como canales de datos que conectan a los editores con los suscriptores.

Las principales ventajas de usar un observable son:
i) abstrae el estado de su código, por ejemplo, si desea que el controlador de eventos se dispare solo por cada 'enésimo evento, o deje de disparar después de los primeros' n 'eventos, o comience a disparar solo después de los primeros eventos 'n', puede usar los HoFs (filter, takeUntil, skip respectivamente) en lugar de configurar, actualizar y verificar contadores.
ii) mejora la localidad del código: si tiene 5 controladores de eventos diferentes que cambian el estado de un componente, puede fusionar sus observables y definir un solo controlador de eventos en el observable combinado, combinando efectivamente 5 controladores de eventos en 1. Esto lo hace muy Es fácil razonar sobre qué eventos en todo su sistema pueden afectar a un componente, ya que todo está presente en un solo controlador.

  • Un Observable es el dual de un Iterable.

Un Iterable es una secuencia consumida perezosamente: cada elemento es extraído por el iterador cada vez que quiere usarlo y, por lo tanto, la enumeración es impulsada por el consumidor.

Un observable es una secuencia producida de manera perezosa: cada elemento se envía al observador cada vez que se agrega a la secuencia y, por lo tanto, la enumeración es impulsada por el productor.

tldr
fuente
1
Muchas gracias por esta definición directa de un observable y su diferenciación de iterables. Creo que a menudo es muy útil comparar un concepto complejo con su conocido concepto dual para obtener una verdadera comprensión.
2
"Entonces, la idea detrás de FRP es que, en lugar de procesar cada evento individual, cree una secuencia de eventos (implementada con un * observable) y aplique HoFs a eso en su lugar". Podría estar equivocado, pero creo que esto no es en realidad FRP, sino más bien una buena abstracción sobre el patrón de diseño de Observer que permite operaciones funcionales a través de HoF (¡lo cual es genial!) Mientras todavía está destinado a usarse con código imperativo. Discusión sobre el tema - lambda-the-ultimate.org/node/4982
nqe
18

¡Amigo, esta es una idea genial! ¿Por qué no me enteré de esto en 1998? De todos modos, aquí está mi interpretación del tutorial de Fran . Las sugerencias son bienvenidas, estoy pensando en iniciar un motor de juego basado en esto.

import pygame
from pygame.surface import Surface
from pygame.sprite import Sprite, Group
from pygame.locals import *
from time import time as epoch_delta
from math import sin, pi
from copy import copy

pygame.init()
screen = pygame.display.set_mode((600,400))
pygame.display.set_caption('Functional Reactive System Demo')

class Time:
    def __float__(self):
        return epoch_delta()
time = Time()

class Function:
    def __init__(self, var, func, phase = 0., scale = 1., offset = 0.):
        self.var = var
        self.func = func
        self.phase = phase
        self.scale = scale
        self.offset = offset
    def copy(self):
        return copy(self)
    def __float__(self):
        return self.func(float(self.var) + float(self.phase)) * float(self.scale) + float(self.offset)
    def __int__(self):
        return int(float(self))
    def __add__(self, n):
        result = self.copy()
        result.offset += n
        return result
    def __mul__(self, n):
        result = self.copy()
        result.scale += n
        return result
    def __inv__(self):
        result = self.copy()
        result.scale *= -1.
        return result
    def __abs__(self):
        return Function(self, abs)

def FuncTime(func, phase = 0., scale = 1., offset = 0.):
    global time
    return Function(time, func, phase, scale, offset)

def SinTime(phase = 0., scale = 1., offset = 0.):
    return FuncTime(sin, phase, scale, offset)
sin_time = SinTime()

def CosTime(phase = 0., scale = 1., offset = 0.):
    phase += pi / 2.
    return SinTime(phase, scale, offset)
cos_time = CosTime()

class Circle:
    def __init__(self, x, y, radius):
        self.x = x
        self.y = y
        self.radius = radius
    @property
    def size(self):
        return [self.radius * 2] * 2
circle = Circle(
        x = cos_time * 200 + 250,
        y = abs(sin_time) * 200 + 50,
        radius = 50)

class CircleView(Sprite):
    def __init__(self, model, color = (255, 0, 0)):
        Sprite.__init__(self)
        self.color = color
        self.model = model
        self.image = Surface([model.radius * 2] * 2).convert_alpha()
        self.rect = self.image.get_rect()
        pygame.draw.ellipse(self.image, self.color, self.rect)
    def update(self):
        self.rect[:] = int(self.model.x), int(self.model.y), self.model.radius * 2, self.model.radius * 2
circle_view = CircleView(circle)

sprites = Group(circle_view)
running = True
while running:
    for event in pygame.event.get():
        if event.type == QUIT:
            running = False
        if event.type == KEYDOWN and event.key == K_ESCAPE:
            running = False
    screen.fill((0, 0, 0))
    sprites.update()
    sprites.draw(screen)
    pygame.display.flip()
pygame.quit()

En resumen: si cada componente se puede tratar como un número, todo el sistema se puede tratar como una ecuación matemática, ¿verdad?

Dan Ross
fuente
1
Esto es un poco tarde, pero de todos modos ... Frag es un juego que usa FRP .
arx
14

El libro de Paul Hudak, The Haskell School of Expression , no solo es una buena introducción a Haskell, sino que también pasa una buena cantidad de tiempo en FRP. Si es un principiante con FRP, lo recomiendo encarecidamente para darle una idea de cómo funciona FRP.

También existe lo que parece una nueva reescritura de este libro (publicado en 2011, actualizado en 2014), The Haskell School of Music .

cjs
fuente
10

Según las respuestas anteriores, parece que matemáticamente, simplemente pensamos en un orden superior. En lugar de pensar que un valor x tiene tipo X , pensamos en una función x : TX , donde T es el tipo de tiempo, ya sean los números naturales, los enteros o el continuo. Ahora cuando escribimos y : = x + 1 en el lenguaje de programación, en realidad queremos decir la ecuación y ( t ) = x ( t ) + 1.

Yuning
fuente
9

Actúa como una hoja de cálculo como se señaló. Por lo general, se basa en un marco basado en eventos.

Como con todos los "paradigmas", su novedad es discutible.

Según mi experiencia con las redes de actores de flujo distribuido, puede ser fácilmente presa de un problema general de consistencia de estado en la red de nodos, es decir, termina con mucha oscilación y atrapamiento en bucles extraños.

Esto es difícil de evitar ya que algunas semánticas implican bucles referenciales o transmisión, y puede ser bastante caótico ya que la red de actores converge (o no) en algún estado impredecible.

Del mismo modo, algunos estados pueden no alcanzarse, a pesar de tener bordes bien definidos, porque el estado global se aleja de la solución. 2 + 2 pueden o no llegar a ser 4 dependiendo de cuándo los 2 se convirtieron en 2 y si se mantuvieron así. Las hojas de cálculo tienen relojes síncronos y detección de bucles. Los actores distribuidos generalmente no lo hacen.

Todo muy divertido :).

emperador
fuente
7

Este artículo de Andre Staltz es la mejor y más clara explicación que he visto hasta ahora.

Algunas citas del artículo:

La programación reactiva es la programación con flujos de datos asíncronos.

Además de eso, se le ofrece una increíble caja de herramientas de funciones para combinar, crear y filtrar cualquiera de esas transmisiones.

Aquí hay un ejemplo de los diagramas fantásticos que forman parte del artículo:

Haga clic en diagrama de flujo de eventos

Gigante verde
fuente
5

Se trata de transformaciones de datos matemáticos a lo largo del tiempo (o ignorar el tiempo).

En código esto significa pureza funcional y programación declarativa.

Los errores estatales son un gran problema en el paradigma imperativo estándar. Varios bits de código pueden cambiar algún estado compartido en diferentes "momentos" en la ejecución de los programas. Esto es difícil de manejar.

En FRP, usted describe (como en la programación declarativa) cómo se transforman los datos de un estado a otro y qué los desencadena. Esto le permite ignorar el tiempo porque su función simplemente reacciona a sus entradas y usa sus valores actuales para crear una nueva. Esto significa que el estado está contenido en el gráfico (o árbol) de los nodos de transformación y es funcionalmente puro.

Esto reduce enormemente la complejidad y el tiempo de depuración.

Piense en la diferencia entre A = B + C en matemáticas y A = B + C en un programa. En matemáticas, estás describiendo una relación que nunca cambiará. En un programa, dice que "ahora mismo" A es B + C. Pero el siguiente comando podría ser B ++, en cuyo caso A no es igual a B + C. En matemáticas o programación declarativa, A siempre será igual a B + C, sin importar en qué momento lo solicite.

Entonces, eliminando las complejidades del estado compartido y cambiando los valores con el tiempo. Su programa es mucho más fácil de razonar.

Un EventStream es un EventStream + alguna función de transformación.

Un comportamiento es un EventStream + Algún valor en la memoria.

Cuando se activa el evento, el valor se actualiza ejecutando la función de transformación. El valor que esto produce se almacena en la memoria de comportamientos.

Los comportamientos se pueden componer para producir nuevos comportamientos que son una transformación en N otros comportamientos. Este valor compuesto se volverá a calcular a medida que se activen los eventos de entrada (comportamientos).

"Dado que los observadores no tienen estado, a menudo necesitamos varios de ellos para simular una máquina de estados como en el ejemplo de arrastre. Tenemos que guardar el estado donde sea accesible para todos los observadores involucrados, como en la ruta variable anterior".

Cita de - Deprecating The Observer Pattern http://infoscience.epfl.ch/record/148043/files/DeprecatingObserversTR2010.pdf

Jay Shepherd
fuente
Así es exactamente como me siento acerca de la programación declarativa, y usted simplemente describe la idea mejor que yo.
neevek
2

La explicación breve y clara sobre la Programación reactiva aparece en Cyclejs - Programación reactiva , utiliza muestras simples y visuales.

Un [módulo / componente / objeto] es reactivo significa que es totalmente responsable de administrar su propio estado al reaccionar a eventos externos.

¿Cuál es el beneficio de este enfoque? Es la Inversión de Control , principalmente porque [módulo / Componente / objeto] es responsable de sí mismo, mejorando la encapsulación utilizando métodos privados contra los públicos.

Es un buen punto de inicio, no una fuente completa de conocimiento. A partir de ahí, podría saltar a papeles más complejos y profundos.

pdorgambida
fuente
0

Echa un vistazo a Rx, Extensiones reactivas para .NET. Señalan que con IEnumerable básicamente estás 'tirando' de una transmisión. Las consultas de Linq sobre IQueryable / IEnumerable son operaciones de conjunto que 'succionan' los resultados de un conjunto. Pero con los mismos operadores sobre IObservable puede escribir consultas de Linq que 'reaccionen'.

Por ejemplo, podría escribir una consulta Linq como (desde m en MyObservableSetOfMouseMovements donde mX <100 y mY <100 seleccionan un nuevo Punto (mX, mY)).

y con las extensiones Rx, eso es todo: tiene un código de interfaz de usuario que reacciona al flujo entrante de movimientos del mouse y dibuja cada vez que se encuentra en el cuadro de 100,100 ...

Centinela
fuente
0

FRP es una combinación de programación funcional (paradigma de programación construido sobre la idea de que todo es una función) y paradigma de programación reactiva (construido sobre la idea de que todo es una corriente (observador y filosofía observable)). Se supone que es el mejor de los mundos.

Para empezar, mira la publicación de Andre Staltz sobre programación reactiva.

Krishna Ganeriwal
fuente