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 del cambio. Con este paradigma, es posible expresar flujos de datos estáticos (p. ej., matrices) o dinámicos (p. ej., 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 de los cambios. flujo de datos. [ cita necesaria ]

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 cse pueden cambiar sin ningún efecto sobre el valor de a. Por otro lado, en la programación reactiva , el valor de ase actualiza automáticamente cada vez que los valores de bo ccambian, sin que el programa tenga que volver a expresar explícitamente la declaración a := b + cpara reasignar el valor de a. [ cita necesaria ]

var b = 1 var c = 2 var a = b + c b = 10 consola . 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 sólo cuando se inicializa explícitamente, sino también cuando se hace referencia a variables ( en el lado derecho del operador) se cambian var b = 1 var c = 2 var a $ = b + c b = 10 consola . iniciar sesión ( 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 necesaria ]

La programación reactiva se ha propuesto como una forma de simplificar la creación de interfaces de usuario interactivas y animaciones del sistema casi en tiempo real. [ cita necesaria ]

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.

Se emplean varios enfoques populares en la creación de lenguajes de programación reactivos. Un enfoque es la especificación de lenguajes dedicados que sean específicos de varias restricciones de dominio . Estas restricciones suelen caracterizarse por una descripción de hardware o informática integrada en tiempo real. Otro enfoque implica la especificación de lenguajes de propósito general que incluyan soporte para la reactividad. Otros enfoques se articulan en la definición y el uso de bibliotecas de programación , o lenguajes integrados de dominio específico , que permiten la reactividad junto con el lenguaje de programación o además de é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 especificidad pueden resultar en el deterioro de la aplicabilidad general de un lenguaje.

Modelos de programación y semántica.

Una variedad de modelos y semánticas gobiernan la programación reactiva. Podemos dividirlos libremente en las siguientes dimensiones:

Técnicas de implementación y desafíos.

Esencia de implementaciones.

Los tiempos de ejecución del lenguaje de programación reactivo 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. Dicho tiempo de ejecución emplea dicho gráfico para ayudarlo a realizar un seguimiento de los diversos cálculos, que deben ejecutarse de nuevo, una vez que una entrada involucrada cambia de valor.

Cambiar algoritmos de propagación

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

¿Qué empujar?

A nivel de implementación, la reacción a un evento consiste en la propagación a través de la información de un gráfico, lo 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 su reejecución. Dichos cálculos generalmente se caracterizan por el cierre transitivo del cambio (es decir, el conjunto completo de dependencias transitivas a las 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 el 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 prolifera a lo largo de los bordes de un gráfico, que consisten únicamente en deltas que describen cómo se cambió 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 la computación incremental , cuyo enfoque requiere la satisfacción del tiempo de ejecución que involucra el problema de visualización y actualización . Este problema se caracteriza notoriamente por el uso de entidades de bases de datos , que son responsables del mantenimiento de las 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 ellos y realizar las modificaciones correspondientes. por ejemplo, dos cambios en el lote pueden cancelarse entre sí y, por tanto, simplemente ignorarse. Otro enfoque disponible se describe como propagación de notificaciones de nulidad . Este enfoque hace que los nodos con entradas no válidas obtengan actualizaciones, lo que resulta en 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 devolución de llamada, se deja en su lugar. Sin embargo, hacer que las devoluciones de llamada sean funcionales (es decir, devolver un valor de estado en lugar de un valor unitario) requiere que dichas devoluciones de llamada se vuelvan compositivas.
  2. Un gráfico de dependencias es específico del programa y lo genera un programador. Esto facilita abordar la inversión de control de la devolución de llamada de dos maneras: o se especifica explícitamente un gráfico (normalmente usando un lenguaje de dominio específico (DSL), que puede estar incrustado), o un gráfico se define implícitamente con expresión y generación usando un método efectivo. , lenguaje arquetípico .

Desafíos de implementación en la 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 la hora 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 el primero se evalúa antes que el segundo, entonces este invariante se mantendrá. Sin embargo, si el 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 falla .

Algunos lenguajes reactivos no tienen fallas y demuestran esta propiedad [ cita necesaria ] . Esto generalmente se logra ordenando topológicamente las expresiones y actualizando los valores en orden topológico. Sin embargo, esto puede tener implicaciones en el rendimiento, como retrasar la entrega de valores (debido al orden de propagación). Por lo tanto, en algunos casos, los lenguajes reactivos permiten fallas técnicas y los desarrolladores deben ser conscientes de la posibilidad de que los valores no correspondan temporalmente con el código fuente del programa y que algunas expresiones puedan evaluarse varias veces (por ejemplo, t > secondspueden evaluarse dos veces: una cuando el llega el nuevo valor de seconds, y una vez más cuando tse actualiza).

Dependencias cíclicas

La clasificación topológica de las dependencias depende de que el gráfico de dependencia sea un gráfico acíclico dirigido (DAG). En la práctica, un programa puede definir un gráfico de dependencia que tenga ciclos. Por lo general, los lenguajes de programación reactivos 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. Normalmente, los lenguajes proporcionan un operador como delayel que utiliza el mecanismo de actualización para este propósito, ya que delayimplica que lo que sigue debe evaluarse en el "próximo paso" (permitiendo que finalice la evaluación actual).

Interacción con el estado mutable

Los lenguajes reactivos suelen asumir que sus expresiones son puramente funcionales . Esto permite que un mecanismo de actualización elija diferentes órdenes en los que realizar actualizaciones y deje el orden específico sin especificar (lo que permite 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 hacer que esta interacción sea fluida sigue siendo un problema abierto.

En algunos casos, es posible tener soluciones parciales basadas en principios. Dos de estas soluciones incluyen:

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 secondshay 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, de la que t + 1luego depende. Por 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 las expresiones cada vez o mantener el nodo de una expresión construido pero inactivo; en este último caso, asegúrese de que no participen en el cálculo cuando se supone que no están activos.

Conceptos

Grados de explicitud

Los lenguajes de programación reactivos pueden variar desde lenguajes muy explícitos en los que los flujos de datos se configuran mediante flechas, hasta lenguajes implícitos en los que los flujos de datos se derivan de construcciones de lenguaje que parecen similares a las de la programación imperativa o funcional. Por ejemplo, en la programación reactiva funcional (FRP) levantada implícitamente, una llamada a función podría causar implícitamente que se construya 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 de flujo de datos sean implícitas y 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 comunes 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 estáticamente, o dinámica donde los flujos de datos pueden cambiar durante la ejecución de un programa.

El uso de interruptores de datos en el gráfico de flujo de datos podría, hasta cierto punto, hacer que un gráfico de flujo de datos estático parezca dinámico y desdibujar ligeramente la distinción. Sin embargo, la verdadera programación reactiva 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 podrían usarse 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

Idealmente, todos los cambios de datos se propagan instantáneamente, pero esto no se puede garantizar en la práctica. En cambio, podría ser necesario asignar diferentes prioridades de evaluación a diferentes partes del gráfico de flujo de datos. Esto se puede llamar programación reactiva diferenciada . [ cita necesaria ]

Por ejemplo, en un procesador de textos, no es necesario que la corrección de errores ortográficos esté totalmente sincronizada con la inserción de caracteres. En este caso, la programación reactiva diferenciada podría usarse potencialmente para darle al corrector ortográfico una prioridad menor, permitiendo retrasarlo y manteniendo instantáneos otros flujos de datos.

Sin embargo, dicha diferenciación introduce una complejidad de diseño adicional. Por ejemplo, decidir cómo definir las diferentes áreas de flujo de datos y cómo manejar el paso de eventos entre diferentes á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 pila. En cambio, cuando se cambian algunos datos, el cambio se propaga a todos los datos que se derivan parcial o completamente de los datos que se cambiaron. Esta propagación del cambio podría lograrse 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 simplemente propagar ingenuamente un cambio utilizando una pila, debido a la posible complejidad exponencial de la actualización si la estructura de datos tiene una determinada forma. Una de esas formas puede describirse como "forma de diamante repetida" 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 podría superarse propagando la invalidación solo cuando algunos datos aún no estén invalidados y luego revalidando los datos cuando sea necesario mediante una evaluación diferida .

Un problema inherente a la programación reactiva es que la mayoría de los cálculos que serían evaluados y olvidados en un lenguaje de programación normal deben representarse en la memoria como estructuras de datos. [ cita necesaria ] Esto podría hacer que la programación reactiva consuma mucha memoria. Sin embargo, la investigación sobre lo que se llama 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 de observador.

La programación reactiva tiene similitudes principales con el patrón de observador comúnmente utilizado en la programación orientada a objetos . Sin embargo, integrar los conceptos de flujo de datos en el lenguaje de programación facilitaría su expresión y, por tanto, podría aumentar la granularidad del gráfico de flujo de datos. Por ejemplo, el patrón de observador comúnmente describe 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.

Enfoques

Imperativo

Es posible fusionar la programación reactiva con la programación imperativa ordinaria . En tal paradigma, los programas imperativos operan sobre estructuras de datos reactivas. [5] Tal configuración es análoga a la programación de restricciones imperativas ; sin embargo, mientras que la programación de restricciones imperativa gestiona restricciones de flujo de datos bidireccionales, la programación reactiva imperativa 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 sea la siguiente: en lugar de métodos y campos, los objetos tienen reacciones que se reevalúan automáticamente cuando las otras reacciones de las que dependen han sido modificadas. [ cita necesaria ]

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 sobre programación funcional .

basado en actores

Se han propuesto actores para diseñar sistemas reactivos, a menudo en combinación con programación reactiva funcional (FRP) y 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 sólo 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

Ver también

Referencias

  1. ^ Trellis, Model-view-controller y el patrón de observador, Tele community, archivado desde el original el 3 de marzo de 2016 , consultado el 27 de julio de 2009.
  2. ^ "Integración de 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 fronteras estatales: adaptación de marcos orientados a objetos a lenguajes reactivos funcionales". cs.brown.edu . Archivado desde el original el 9 de octubre de 2016 . Consultado el 9 de octubre de 2016 .
  4. ^ Burchett, Kimberley; Cooper, Gregory H; Krishnamurthi, Shriram, "Reducción: una técnica de optimización estática para una reactividad funcional transparente", 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) del 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, S2CID  7285961.
  6. ^ Van den Vonder, Sam; Renaux, Thierry; Oeyen, Bjarno; De Koster, Joeri; De Meuter, Wolfgang (2020), "Abordar el equipo incómodo para la programación reactiva: el modelo actor-reactor", Actas internacionales de informática de Leibniz (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 2021-10-20 , 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 del conocimiento reactivo mediante bandadas", El arte, la ciencia y la ingeniería de la programación, 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 2023-03-15 , consultado el 2023-03-16
  8. ^ Shibanai, Kazuhiro; Watanabe, Takuo (2018), "Programación reactiva funcional distribuida en tiempo de ejecución basado en actores", Actas del octavo 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", Revista de métodos lógicos y algebraicos en programación , vol. 100, págs. 113–29, doi : 10.1016/j.jlamp.2018.04.002 , S2CID  52932824.

enlaces externos