stringtranslate.com

Programación reactiva

En informática , la programación reactiva es un paradigma de programación declarativa que se ocupa de los flujos de datos y la propagación de cambios. Con este paradigma, es posible expresar flujos de datos estáticos (por ejemplo, matrices) o dinámicos (por ejemplo, emisores de eventos) con facilidad, y también comunicar que existe una dependencia inferida dentro del modelo de ejecución asociado, lo que facilita la propagación automática del flujo de datos modificado. [ cita requerida ]

Por ejemplo, en una configuración de programación imperativaa := b + c , significaría que ase le asigna el resultado de b + cen el instante en que se evalúa la expresión y, posteriormente, los valores de by cpueden cambiarse sin que esto afecte al valor de a. Por otro lado, en la programación reactivaa , el valor de se actualiza automáticamente siempre que cambian los valores de bo c, sin que el programa tenga que volver a formular explícitamente la declaración a := b + cpara volver a asignar el valor de a. [ cita requerida ]

var b = 1 var c = 2 var a = b + c b = 10 console.log ( a ) // 3 (no 12 porque "=" no es un operador de asignación reactiva )              // ahora imagina que tienes un operador especial "$=" que cambia el valor de una variable (ejecuta código en el lado derecho del operador y asigna el resultado a la variable del lado izquierdo) no solo cuando se inicializa explícitamente, sino también cuando se cambian las variables referenciadas (en el lado derecho del operador ) var b = 1 var c = 2 var a $ = b + c b = 10 console.log ( a ) // 12              

Otro ejemplo es un lenguaje de descripción de hardware como Verilog , donde la programación reactiva permite modelar los cambios a medida que se propagan a través de los circuitos. [ cita requerida ]

Se ha propuesto la programación reactiva como una forma de simplificar la creación de interfaces de usuario interactivas y animaciones de sistemas casi en tiempo real. [ cita requerida ]

Por ejemplo, en una arquitectura modelo-vista-controlador (MVC), la programación reactiva puede facilitar que los cambios en un modelo subyacente se reflejen automáticamente en una vista asociada . [1]

Enfoques para la creación de lenguajes de programación reactivos

En la creación de lenguajes de programación reactivos se emplean varios enfoques populares. Un enfoque es la especificación de lenguajes dedicados que son específicos para varias restricciones de dominio . Dichas restricciones generalmente se caracterizan por computación embebida en tiempo real o descripción de hardware. Otro enfoque implica la especificación de lenguajes de propósito general que incluyen soporte para reactividad. Otros enfoques se articulan en la definición y el uso de bibliotecas de programación , o lenguajes embebidos específicos del dominio , que permiten la reactividad junto con el lenguaje de programación o sobre él. La especificación y el uso de estos diferentes enfoques dan como resultado compensaciones en la capacidad del lenguaje . En general, cuanto más restringido es un lenguaje, más pueden sus compiladores y herramientas de análisis asociados informar a los desarrolladores (por ejemplo, al realizar análisis para determinar si los programas pueden ejecutarse en tiempo real). Las compensaciones funcionales en la especificidad pueden dar como resultado el deterioro de la aplicabilidad general de un lenguaje.

Modelos de programación y semántica

La programación reactiva está regida por una variedad de modelos y semánticas. Podemos dividirlos libremente en las siguientes dimensiones:

Técnicas de implementación y desafíos

Esencia de las implementaciones

Los tiempos de ejecución de los lenguajes de programación reactiva se representan mediante un gráfico que identifica las dependencias entre los valores reactivos involucrados. En dicho gráfico, los nodos representan el acto de calcular y los bordes modelan las relaciones de dependencia. Dichos tiempos de ejecución emplean dicho gráfico para ayudar a realizar un seguimiento de los diversos cálculos que deben ejecutarse nuevamente una vez que una entrada involucrada cambia de valor.

Algoritmos de propagación de cambios

Los enfoques más comunes para la propagación de datos son:

¿Qué empujar?

En el nivel de implementación, la reacción a eventos consiste en la propagación a través de la información de un gráfico, que caracteriza la existencia de un cambio. En consecuencia, los cálculos que se ven afectados por dicho cambio quedan obsoletos y deben marcarse para volver a ejecutarse. Dichos cálculos suelen caracterizarse por el cierre transitivo del cambio (es decir, el conjunto completo de dependencias transitivas que afecta una fuente) en su fuente asociada. La propagación del cambio puede entonces conducir a una actualización en el valor de los sumideros del gráfico .

La información propagada por un gráfico puede consistir en el estado completo de un nodo, es decir, el resultado del cálculo del nodo involucrado. En tales casos, se ignora la salida anterior del nodo. Otro método implica la propagación delta , es decir, la propagación de cambios incrementales . En este caso, la información se propaga a lo largo de los bordes de un gráfico, que consisten solo en deltas que describen cómo se modificó el nodo anterior. Este enfoque es especialmente importante cuando los nodos contienen grandes cantidades de datos de estado , que de otro modo serían costosos de volver a calcular desde cero.

La propagación delta es esencialmente una optimización que se ha estudiado ampliamente a través de la disciplina de computación incremental , cuyo enfoque requiere la satisfacción del tiempo de ejecución que involucra el problema de actualización de vista . Este problema se caracteriza por el uso de entidades de base de datos , que son responsables del mantenimiento de vistas de datos cambiantes.

Otra optimización común es el empleo de la acumulación de cambios unarios y la propagación por lotes . Esta solución puede ser más rápida porque reduce la comunicación entre los nodos involucrados. Luego se pueden emplear estrategias de optimización que razonan sobre la naturaleza de los cambios contenidos en el lote y realizan modificaciones en consecuencia (por ejemplo, dos cambios en el lote pueden cancelarse entre sí y, por lo tanto, simplemente ignorarse). Otro enfoque disponible se describe como propagación de notificación de invalidez . Este enfoque hace que los nodos con entradas no válidas extraigan actualizaciones, lo que da como resultado la actualización de sus propias salidas.

Hay dos formas principales empleadas en la construcción de un gráfico de dependencia :

  1. El gráfico de dependencias se mantiene implícitamente dentro de un bucle de eventos . El registro de devoluciones de llamadas explícitas da como resultado la creación de dependencias implícitas. Por lo tanto, la inversión de control , que se induce mediante la devolución de llamada, se deja así en su lugar. Sin embargo, hacer que las devoluciones de llamadas sean funcionales (es decir, devolver el valor del estado en lugar del valor de la unidad) requiere que dichas devoluciones de llamadas se vuelvan compositivas.
  2. Un gráfico de dependencias es específico del programa y lo genera un programador. Esto facilita el direccionamiento de la inversión de control de la devolución de llamada de dos maneras: o bien se especifica un gráfico explícitamente (normalmente mediante un lenguaje específico del dominio [DSL], que puede estar integrado), o bien se define un gráfico implícitamente con expresión y generación mediante un lenguaje arquetípico eficaz .

Desafíos de implementación en programación reactiva

Fallos

Al propagar cambios, es posible elegir órdenes de propagación de modo que el valor de una expresión no sea una consecuencia natural del programa fuente. Podemos ilustrar esto fácilmente con un ejemplo. Supongamos secondsque es un valor reactivo que cambia cada segundo para representar el tiempo actual (en segundos). Considere esta expresión:

t = segundos + 1g = (t > segundos)

Como tsiempre debe ser mayor que seconds, esta expresión siempre debe evaluarse como un valor verdadero. Desafortunadamente, esto puede depender del orden de evaluación. Cuando secondscambia, se deben actualizar dos expresiones: seconds + 1y la condicional. Si la primera se evalúa antes que la segunda, entonces se cumplirá esta invariante. Sin embargo, si la condicional se actualiza primero, utilizando el valor anterior de ty el nuevo valor de seconds, entonces la expresión se evaluará como un valor falso. Esto se llama un error .

Algunos lenguajes reactivos no tienen fallos y demuestran esta propiedad [ cita requerida ] . Esto se logra generalmente ordenando topológicamente las expresiones y actualizando los valores en orden topológico. Sin embargo, esto puede tener implicaciones en el rendimiento, como demorar la entrega de valores (debido al orden de propagación). Por lo tanto, en algunos casos, los lenguajes reactivos permiten fallos y los desarrolladores deben ser conscientes de la posibilidad de que los valores no se correspondan temporalmente con la fuente del programa y de que algunas expresiones se evalúen varias veces (por ejemplo, pueden t > secondsevaluarse dos veces: una cuando llega el nuevo valor secondsy otra cuando tse actualiza).

Dependencias cíclicas

La ordenación topológica de las dependencias depende de que el gráfico de dependencias sea un gráfico acíclico dirigido (DAG). En la práctica, un programa puede definir un gráfico de dependencias que tenga ciclos. Por lo general, los lenguajes de programación reactiva esperan que dichos ciclos se "rompan" colocando algún elemento a lo largo de un "borde posterior" para permitir que finalice la actualización reactiva. Por lo general, los lenguajes proporcionan un operador como delayel que utiliza el mecanismo de actualización para este propósito, ya que a delayimplica que lo que sigue debe evaluarse en el "próximo paso de tiempo" (lo que permite que finalice la evaluación actual).

Interacción con el estado mutable

Los lenguajes reactivos suelen suponer que sus expresiones son puramente funcionales . Esto permite que un mecanismo de actualización elija diferentes órdenes en los que realizar las actualizaciones y deje el orden específico sin especificar (lo que permite las optimizaciones). Sin embargo, cuando un lenguaje reactivo está integrado en un lenguaje de programación con estado, los programadores pueden realizar operaciones mutables. Cómo lograr que esta interacción sea fluida sigue siendo un problema abierto.

En algunos casos, es posible encontrar soluciones parciales basadas en principios. Dos de esas soluciones son:

Actualización dinámica del gráfico de dependencias

En algunos lenguajes reactivos, el gráfico de dependencias es estático , es decir, el gráfico es fijo durante toda la ejecución del programa. En otros lenguajes, el gráfico puede ser dinámico , es decir, puede cambiar a medida que se ejecuta el programa. Para un ejemplo simple, considere este ejemplo ilustrativo (donde secondses un valor reactivo):

t= si ((segundos mod 2) == 0): segundos + 1 demás: segundos - 1 fint+1

Cada segundo, el valor de esta expresión cambia a una expresión reactiva diferente, que t + 1luego depende de ella. Por lo tanto, el gráfico de dependencias se actualiza cada segundo.

Permitir la actualización dinámica de dependencias proporciona un poder expresivo significativo (por ejemplo, las dependencias dinámicas ocurren rutinariamente en programas de interfaz gráfica de usuario (GUI)). Sin embargo, el motor de actualización reactiva debe decidir si reconstruir expresiones cada vez o mantener el nodo de una expresión construido pero inactivo; en este último caso, asegurarse de que no participen en el cálculo cuando no se supone que estén activos.

Conceptos

Grados de explicitud

Los lenguajes de programación reactiva pueden ser muy explícitos, en los que los flujos de datos se configuran mediante flechas, o implícitos, en los que los flujos de datos se derivan de construcciones del lenguaje que parecen similares a las de la programación imperativa o funcional. Por ejemplo, en una programación reactiva funcional (FRP) implícitamente elevada, una llamada a una función puede provocar implícitamente la construcción de un nodo en un gráfico de flujo de datos. Las bibliotecas de programación reactiva para lenguajes dinámicos (como las bibliotecas Lisp "Cells" y Python "Trellis") pueden construir un gráfico de dependencia a partir del análisis en tiempo de ejecución de los valores leídos durante la ejecución de una función, lo que permite que las especificaciones del flujo de datos sean tanto implícitas como dinámicas.

A veces, el término programación reactiva se refiere al nivel arquitectónico de la ingeniería de software, donde los nodos individuales en el gráfico de flujo de datos son programas ordinarios que se comunican entre sí.

Estático o dinámico

La programación reactiva puede ser puramente estática, donde los flujos de datos se configuran de forma estática, o dinámica, donde los flujos de datos pueden cambiar durante la ejecución de un programa.

El uso de conmutadores de datos en el gráfico de flujo de datos podría hacer que un gráfico de flujo de datos estático parezca dinámico, y desdibujar ligeramente la distinción. Sin embargo, la programación reactiva verdaderamente dinámica podría utilizar la programación imperativa para reconstruir el gráfico de flujo de datos.

Programación reactiva de orden superior

Se podría decir que la programación reactiva es de orden superior si respalda la idea de que los flujos de datos se pueden utilizar para construir otros flujos de datos. Es decir, el valor resultante de un flujo de datos es otro gráfico de flujo de datos que se ejecuta utilizando el mismo modelo de evaluación que el primero.

Diferenciación del flujo de datos

Lo ideal sería que todos los cambios de datos se propagaran instantáneamente, pero esto no se puede garantizar en la práctica. En cambio, podría ser necesario dar a las distintas partes del gráfico de flujo de datos diferentes prioridades de evaluación. Esto se puede llamar programación reactiva diferenciada . [ cita requerida ]

Por ejemplo, en un procesador de textos, la corrección de errores ortográficos no tiene por qué estar totalmente sincronizada con la inserción de caracteres. En este caso, se podría utilizar una programación reactiva diferenciada para dar una prioridad menor al corrector ortográfico, lo que permitiría retrasarlo y mantener instantáneos los demás flujos de datos.

Sin embargo, esta diferenciación introduce una complejidad de diseño adicional. Por ejemplo, hay que decidir cómo definir las distintas áreas de flujo de datos y cómo gestionar el paso de eventos entre distintas áreas de flujo de datos.

Modelos de evaluación de programación reactiva

La evaluación de programas reactivos no se basa necesariamente en cómo se evalúan los lenguajes de programación basados ​​en pilas. En cambio, cuando se modifican algunos datos, el cambio se propaga a todos los datos que se derivan parcial o totalmente de los datos que se modificaron. Esta propagación del cambio se puede lograr de varias maneras, donde quizás la forma más natural sea un esquema de invalidación/revalidación diferida.

Podría resultar problemático propagar un cambio de manera ingenua utilizando una pila, debido a la potencial complejidad exponencial de actualización si la estructura de datos tiene una forma determinada. Una de esas formas se puede describir como "forma de rombos repetidos", y tiene la siguiente estructura: A n →B n →A n+1 , A n →C n →A n+1 , donde n=1,2... Este problema se podría superar propagando la invalidación solo cuando algunos datos no estén ya invalidados y, posteriormente, volviendo a validar los datos cuando sea necesario utilizando una evaluación diferida .

Un problema inherente a la programación reactiva es que la mayoría de los cálculos que se evaluarían y olvidarían en un lenguaje de programación normal, necesitan representarse en la memoria como estructuras de datos. [ cita requerida ] Esto podría hacer que la programación reactiva consuma mucha memoria. Sin embargo, la investigación sobre lo que se denomina reducción podría potencialmente superar este problema. [4]

Por otro lado, la programación reactiva es una forma de lo que podría describirse como "paralelismo explícito" [ cita requerida ] , y por lo tanto podría ser beneficiosa para utilizar el poder del hardware paralelo.

Similitudes con el patrón del observador

La programación reactiva tiene similitudes principales con el patrón observador que se usa comúnmente en la programación orientada a objetos . Sin embargo, la integración de los conceptos de flujo de datos en el lenguaje de programación facilitaría su expresión y, por lo tanto, podría aumentar la granularidad del gráfico de flujo de datos. Por ejemplo, el patrón observador describe comúnmente flujos de datos entre objetos/clases completos, mientras que la programación reactiva orientada a objetos podría apuntar a los miembros de objetos/clases.

Aproches

Imperativo

Es posible fusionar la programación reactiva con la programación imperativa ordinaria . En un paradigma de este tipo, los programas imperativos operan sobre estructuras de datos reactivas. [5] Esta configuración es análoga a la programación imperativa con restricciones ; sin embargo, mientras que la programación imperativa con restricciones gestiona restricciones de flujo de datos bidireccionales, la programación imperativa reactiva gestiona restricciones de flujo de datos unidireccionales. Una implementación de referencia es la extensión de tiempo de ejecución Quantum propuesta para JavaScript.

Orientado a objetos

La programación reactiva orientada a objetos (OORP) es una combinación de programación orientada a objetos y programación reactiva. Quizás la forma más natural de hacer tal combinación es la siguiente: en lugar de métodos y campos, los objetos tienen reacciones que se reevalúan automáticamente cuando se modifican las otras reacciones de las que dependen. [ cita requerida ]

Si un lenguaje OORP mantiene sus métodos imperativos, también entraría en la categoría de programación reactiva imperativa.

Funcional

La programación reactiva funcional (FRP) es un paradigma de programación para la programación reactiva en la programación funcional .

Basado en actores

Se ha propuesto que los actores diseñen sistemas reactivos, a menudo en combinación con la programación reactiva funcional (FRP) y los flujos reactivos para desarrollar sistemas reactivos distribuidos. [6] [7] [8] [9]

Basado en reglas

Una categoría relativamente nueva de lenguajes de programación utiliza restricciones (reglas) como concepto principal de programación. Consiste en reacciones a eventos, que mantienen satisfechas todas las restricciones. Esto no solo facilita las reacciones basadas en eventos, sino que hace que los programas reactivos sean fundamentales para la corrección del software. Un ejemplo de un lenguaje de programación reactivo basado en reglas es Ampersand, que se basa en el álgebra de relaciones . [10]

Implementaciones

Véase también

Referencias

  1. ^ Trellis, Modelo-vista-controlador y el patrón observador, Comunidad Tele, archivado desde el original el 2016-03-03 , consultado el 2009-07-27.
  2. ^ "Incorporación de un flujo de datos dinámico en un lenguaje de llamada por valor". cs.brown.edu . Archivado desde el original el 18 de octubre de 2016 . Consultado el 9 de octubre de 2016 .
  3. ^ "Cruzando las fronteras estatales: adaptación de los marcos orientados a objetos a los lenguajes reactivos funcionales". cs.brown.edu . Archivado desde el original el 2016-10-09 . Consultado el 2016-10-09 .
  4. ^ Burchett, Kimberley; Cooper, Gregory H; Krishnamurthi, Shriram, "Lowering: a static optimalization technique for transparent functional reactivity", Actas del simposio ACM SIGPLAN de 2007 sobre evaluación parcial y manipulación de programas basada en semántica (PDF) , págs. 71–80, archivado (PDF) desde el original el 16 de abril de 2016 , consultado el 8 de septiembre de 2014.
  5. ^ Demetrescu, Camil; Finocchi, Irene; Ribichini, Andrea (22 de octubre de 2011), "Programación imperativa reactiva con restricciones de flujo de datos", Actas de la conferencia internacional ACM de 2011 sobre lenguajes y aplicaciones de sistemas de programación orientados a objetos, Oopsla '11, págs. 407–26, arXiv : 1104.2293 , doi : 10.1145/2048066.2048100, ISBN 9781450309400, Número de identificación del sujeto  7285961.
  6. ^ Van den Vonder, Sam; Renaux, Thierry; Oeyen, Bjarno; De Koster, Joeri; De Meuter, Wolfgang (2020), "Abordar el escuadrón incómodo para la programación reactiva: el modelo actor-reactor", Leibniz International Proceedings in Informatics (LIPIcs), vol. 166, págs. 19:1–19:29, doi : 10.4230/LIPIcs.ECOOP.2020.19 , ISBN 9783959771542, S2CID  260445152, archivado desde el original el 20 de octubre de 2021 , consultado el 24 de junio de 2021.
  7. ^ Van den Vonder, Sam; Renaux, Thierry; De Meuter, Wolfgang (2022), "Reactividad a nivel de topología en programas reactivos distribuidos: gestión de conocimiento reactivo mediante bandadas", The Art, Science, and Engineering of Programming, vol. 6, págs. 14:1–14:36, arXiv : 2202.09228 , doi : 10.22152/programming-journal.org/2022/6/14 , archivado desde el original el 15 de marzo de 2023 , consultado el 16 de marzo de 2023
  8. ^ Shibanai, Kazuhiro; Watanabe, Takuo (2018), "Programación reactiva funcional distribuida en tiempo de ejecución basado en actores", Actas del 8.º Taller internacional ACM SIGPLAN sobre programación basada en actores, agentes y control descentralizado, Agere 2018, págs. 13-22, doi : 10.1145/3281366.3281370, ISBN 9781450360661, S2CID  53113447, archivado desde el original el 24 de junio de 2021 , consultado el 24 de junio de 2021
  9. ^ Roestenburg, Raymond; Bakker, Rob; Williams, Rob (2016). "13: Transmisión". Akká en acción . Greenwich, Connecticut, EE.UU.: Manning Publications Co. p. 281.ISBN 978-1-61729-101-2.
  10. ^ Joosten, Stef (2018), "Álgebra de relaciones como lenguaje de programación utilizando el compilador Ampersand", Journal of Logical and Algebraic Methods in Programming , vol. 100, págs. 113-29, doi : 10.1016/j.jlamp.2018.04.002 , S2CID  52932824.

Enlaces externos