En informática , la programación declarativa es un paradigma de programación —un estilo de construcción de la estructura y los elementos de los programas de computadora— que expresa la lógica de un cálculo sin describir su flujo de control . [1]
Muchos lenguajes que aplican este estilo intentan minimizar o eliminar los efectos secundarios describiendo lo que el programa debe lograr en términos del dominio del problema , en lugar de describir cómo lograrlo como una secuencia de las primitivas del lenguaje de programación [2] (el cómo se deja). hasta la implementación del lenguaje ). Esto contrasta con la programación imperativa , que implementa algoritmos en pasos explícitos. [3] [4]
La programación declarativa a menudo considera los programas como teorías de una lógica formal y los cálculos como deducciones en ese espacio lógico. La programación declarativa puede simplificar enormemente la escritura de programas paralelos . [5]
Los lenguajes declarativos comunes incluyen los de lenguajes de consulta de bases de datos (por ejemplo, SQL , XQuery ), expresiones regulares , programación lógica (por ejemplo, Prolog , Datalog , programación de conjuntos de respuestas ), programación funcional , gestión de configuración y sistemas de modelado algebraico .
La programación declarativa a menudo se define como cualquier estilo de programación que no es imperativo. Varias otras definiciones comunes intentan definirlo simplemente contrastándolo con la programación imperativa. Por ejemplo:
Estas definiciones se superponen sustancialmente.
La programación declarativa es un estilo de programación no imperativo en el que los programas describen los resultados deseados sin enumerar explícitamente los comandos o pasos que deben realizarse. Los lenguajes de programación funcional y lógica se caracterizan por un estilo de programación declarativa. En programación lógica, los programas constan de oraciones expresadas en forma lógica, y la computación utiliza esas oraciones para resolver problemas, que también se expresan en forma lógica.
En un lenguaje funcional puro , como Haskell , todas las funciones no tienen efectos secundarios y los cambios de estado solo se representan como funciones que transforman el estado, que se representa explícitamente como un objeto de primera clase en el programa. Aunque los lenguajes funcionales puros no son imperativos, a menudo brindan la posibilidad de describir el efecto de una función como una serie de pasos. Otros lenguajes funcionales, como Lisp , OCaml y Erlang , admiten una combinación de programación funcional y procedimental.
Algunos lenguajes de programación lógica, como Prolog , y lenguajes de consulta de bases de datos, como SQL, aunque en principio son declarativos, también admiten un estilo de programación procedimental.
La programación declarativa es un término general que incluye una serie de paradigmas de programación más conocidos .
La programación de restricciones establece relaciones entre variables en forma de restricciones que especifican las propiedades de la solución objetivo. El conjunto de restricciones se resuelve dando un valor a cada variable para que la solución sea consistente con el número máximo de restricciones. La programación de restricciones a menudo complementa otros paradigmas: programación funcional, lógica o incluso imperativa.
Ejemplos bien conocidos de lenguajes declarativos específicos de dominio (DSL) incluyen el lenguaje de entrada del generador de analizador yacc , QML , el lenguaje de especificación de compilación Make , el lenguaje de gestión de configuración de Puppet , expresiones regulares , registro de datos , programación de conjuntos de respuestas y un subconjunto de SQL ( consultas SELECT, por ejemplo). Los DSL tienen la ventaja de ser útiles sin necesidad de ser necesariamente completos en Turing , lo que facilita que un lenguaje sea puramente declarativo.
Muchos lenguajes de marcado, como HTML , MXML , XAML , XSLT u otros lenguajes de marcado de interfaz de usuario, suelen ser declarativos. HTML, por ejemplo, solo describe lo que debe aparecer en una página web; no especifica ni el flujo de control para representar una página ni las posibles interacciones de la página con un usuario .
A partir de 2013 [update], algunos sistemas de software [ ¿cuáles? ] combinan lenguajes de marcado de interfaz de usuario tradicionales (como HTML) con marcado declarativo que define qué (pero no cómo) deben hacer los sistemas del servidor back-end para admitir la interfaz declarada. Dichos sistemas, que normalmente utilizan un espacio de nombres XML específico de un dominio , pueden incluir abstracciones de la sintaxis de la base de datos SQL o llamadas parametrizadas a servicios web mediante transferencia de estado representacional (REST) y SOAP . [ cita necesaria ]
Los lenguajes de programación funcionales como Haskell , Scheme y ML evalúan expresiones mediante una aplicación de funciones. A diferencia del paradigma relacionado pero más imperativo de la programación procedimental , la programación funcional pone poco énfasis en la secuenciación explícita. En cambio, los cálculos se caracterizan por varios tipos de aplicación y composición de funciones recursivas de orden superior y, como tales, pueden considerarse simplemente como un conjunto de asignaciones entre dominios y codominios . Muchos lenguajes funcionales, incluidos la mayoría de los de las familias ML y Lisp, no son puramente funcionales y, por lo tanto, permiten la introducción de efectos con estado en los programas.
Los Makefiles, por ejemplo, especifican las dependencias de forma declarativa, [7] pero también incluyen una lista imperativa de acciones a realizar. De manera similar, yacc especifica una gramática libre de contexto de forma declarativa, pero incluye fragmentos de código de un lenguaje anfitrión, que generalmente es imperativo (como C ).
Los lenguajes de programación lógica, como Prolog , Datalog y la programación de conjuntos de respuestas , calculan demostrando que un objetivo es una consecuencia lógica del programa o demostrando que el objetivo es verdadero en un modelo definido por el programa. Prolog calcula reduciendo las metas a submetas, de arriba hacia abajo usando razonamiento hacia atrás , mientras que la mayoría de los sistemas Datalog calculan de abajo hacia arriba usando razonamiento directo . Los programas de conjuntos de respuestas suelen utilizar solucionadores SAT para generar un modelo del programa.
Los modelos o representaciones matemáticas de sistemas físicos pueden implementarse en código informático que sea declarativo. El código contiene una serie de ecuaciones, no asignaciones imperativas, que describen ("declaran") las relaciones de comportamiento. Cuando un modelo se expresa en este formalismo, una computadora puede realizar manipulaciones algebraicas para formular mejor el algoritmo de solución. La causalidad matemática suele imponerse en los límites del sistema físico, mientras que la descripción conductual del sistema en sí es declarativa o acausal. Los lenguajes y entornos de modelado declarativos incluyen Analytica , Modelica y Simile. [8]
Lisp es una familia de lenguajes de programación inspirados libremente en la notación matemática y el cálculo lambda de Alonzo Church . Algunos dialectos, como Common Lisp , son principalmente imperativos pero admiten programación funcional. Otros, como Scheme , están diseñados para programación funcional.
En Scheme, la función factorial se puede definir de la siguiente manera:
( define ( factorial n ) ( si ( = n 0 ) 1 ;;; 0! = 1 ( * n ( factorial ( - n 1 ))))) ;;; ¡norte! = norte*(n-1)!
Esto define la función factorial usando su definición recursiva. Por el contrario, es más típico definir un procedimiento para un lenguaje imperativo.
En lisps y cálculo lambda, las funciones son generalmente ciudadanas de primera clase . En términos generales, esto significa que las funciones pueden ser entradas y salidas para otras funciones. Esto puede simplificar la definición de algunas funciones.
Por ejemplo, escribir una función para generar los primeros n números cuadrados en Racket se puede hacer en consecuencia:
( definir ( primeros n-cuadrados n ) ( map ( lambda ( x ) ( * x x ) ;;; Una función que asigna x -> x^2 ( rango n ))) ;;; Lista de los primeros n enteros no negativos
La función de mapa acepta una función y una lista; la salida es una lista de resultados de la función de entrada en cada elemento de la lista de entrada.
ML (1973) [9] significa "Metalenguaje". ML se escribe estáticamente y se pueden anotar argumentos de función y tipos de retorno. [10]
tiempos divertidos_10 ( n : int ) : int = 10 * n ;
ML no se centra tanto en corchetes como Lisp y, en cambio, utiliza una variedad más amplia de sintaxis para codificar la relación entre los elementos del código, en lugar de recurrir al ordenamiento y anidamiento de listas para expresar todo. La siguiente es una aplicación de times_10
:
veces_10 2
Devuelve "20:int", es decir, 20
un valor de tipo int
.
Al igual que Lisp , ML está diseñado para procesar listas, aunque todos los elementos de una lista deben ser del mismo tipo. [11]
Prolog (1972) significa "PROgramación en LOGic". Fue desarrollado para responder preguntas en lenguaje natural , [12] utilizando resolución SL [13] tanto para deducir respuestas a consultas como para analizar y generar oraciones en lenguaje natural.
Los componentes básicos de un programa Prolog son hechos y reglas . Aquí hay un ejemplo simple:
gato ( tom ). % tom es un ratón gato ( jerry ). % jerry es un ratónanimal ( X ) : - gato ( X ). % cada gato es un animal animal ( X ) : - ratón ( X ). % cada ratón es un animalgrande ( X ) : - gato ( X ). % cada gato es grande pequeño ( X ) : - ratón ( X ). % cada ratón es pequeñocomer ( X , Y ) : - ratón ( X ), queso ( Y ). % cada ratón come cada queso come ( X , Y ) : - grande ( X ), pequeño ( Y ). % cada ser grande se come a cada ser pequeño
Dado este programa, la consulta tiene éxito y falla. Además, la consulta tiene éxito con la sustitución de la respuesta .eat(tom,jerry)
eat(jerry,tom)
eat(X,jerry)
X=tom
Prolog ejecuta programas de arriba hacia abajo, utilizando la resolución SLD para razonar hacia atrás , reduciendo las metas a submetas. En este ejemplo, se utiliza la última regla del programa para reducir el objetivo de responder la consulta a los subobjetivos de encontrar primero una X tal que se cumpla y luego mostrar que se cumple. Utiliza repetidamente reglas para reducir aún más los subobjetivos a otros subobjetivos, hasta que finalmente logra unificar todos los subobjetivos con hechos en el programa. Esta estrategia de razonamiento hacia atrás y reducción de objetivos trata las reglas de los programas lógicos como procedimientos y convierte a Prolog en un lenguaje de programación tanto declarativo como procedimental . [14]eat(X,jerry)
big(X)
small(jerry)
La amplia gama de aplicaciones Prolog se destaca en el Libro Año de Prolog, [15] que celebra el 50 aniversario de Prolog.
Los orígenes de Datalog se remontan al comienzo de la programación lógica, pero se identificó como un área separada alrededor de 1977. Sintáctica y semánticamente , es un subconjunto de Prolog. Pero como no tiene términos compuestos , no es Turing completo .
La mayoría de los sistemas Datalog ejecutan programas de abajo hacia arriba, utilizando reglas para razonar hacia adelante , derivando nuevos hechos a partir de hechos existentes y finalizando cuando no hay nuevos hechos que puedan derivarse, o cuando los hechos derivados se unifican con la consulta. En el ejemplo anterior, un sistema de registro de datos típico derivaría primero los nuevos hechos:
animal ( gato ). animal ( jersey ). grande ( tom ). pequeño ( jersey ).
Utilizando estos hechos, derivaría el hecho adicional:
come ( tom , jerry ).
Entonces terminaría, porque no se pueden derivar hechos nuevos y adicionales y porque el hecho recién derivado se unifica con la consulta.
come ( X , jerry ).
Datalog se ha aplicado a problemas tales como integración de datos , extracción de información , redes , seguridad , computación en la nube y aprendizaje automático . [16] [17]
La programación de conjuntos de respuestas (ASP) evolucionó a finales de la década de 1990, basándose en la semántica del modelo estable (conjunto de respuestas) de la programación lógica. Al igual que Datalog, es un subconjunto de Prolog; y, como no tiene términos compuestos, no es Turing completo.
La mayoría de las implementaciones de ASP ejecutan un programa primero "conectándolo a tierra", reemplazando todas las variables en las reglas por constantes en todas las formas posibles y luego usando un solucionador SAT proposicional, como el algoritmo DPLL , para generar uno o más modelos del programa.
Sus aplicaciones están orientadas a la resolución de problemas difíciles de búsqueda y representación del conocimiento . [18] [19]
En este contexto, el criterio para llamar declarativo a un lenguaje de programación es la existencia de una correspondencia clara y matemáticamente establecida entre el lenguaje y la lógica matemática, de modo que una semántica declarativa para el lenguaje pueda basarse en el modelo o en la teoría de prueba (o en ambos). de la lógica.