Programación declarativa
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 informáticos, que expresa la lógica de un cálculo sin describir su flujo de control.
Muchos lenguajes que aplican este estilo intentan minimizar o eliminar los efectos secundarios describiendo qué debe lograr el programa en términos del dominio del problema, en lugar de describir cómo lograr como una secuencia de las primitivas del lenguaje de programación (el cómo queda a cargo de la implementación del lenguaje). Esto contrasta con la programación imperativa, que implementa algoritmos en pasos explícitos.
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.
Los lenguajes declarativos comunes incluyen los lenguajes de consulta de bases de datos (por ejemplo, SQL, XQuery), expresiones regulares, programación lógica, programación funcional y sistemas de administración de configuración.
El término se usa a menudo en contraste con la programación imperativa, que dicta explícitamente los pasos de transformación de su estado.
Definición
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:
- Un programa de alto nivel que describe lo que un cálculo debe realizar.
- Cualquier lenguaje de programación que carece de efectos secundarios (o más específicamente, es referentemente transparente)
- Un lenguaje con una clara correspondencia con la lógica matemática.
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 los pasos que se deben realizar. Los lenguajes de programación lógicos y funcionales se caracterizan por un estilo de programación declarativo. En los lenguajes de programación lógicos, los programas consisten en declaraciones lógicas y el programa se ejecuta buscando pruebas de las declaraciones.
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 proporcionan una facilidad para 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ógicos, como Prolog, y lenguajes de consulta de base de datos, como SQL, aunque declarativos en principio, también admiten un estilo de programación procedimental.
Subparadigmas
La programación declarativa es un término general que incluye una serie de paradigmas de programación más conocidos.
Programación de restricciones
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.
Idiomas específicos del dominio
Ejemplos bien conocidos de lenguajes específicos de dominio (DSL) declarativos incluyen el lenguaje de entrada del generador del analizador yacc, QML, el lenguaje de especificación de compilación Make, el lenguaje de administración de configuración de Puppet, expresiones regulares y un subconjunto de SQL (consultas SELECT, por ejemplo). Los DSL tienen la ventaja de ser útiles y no necesariamente tener que ser Turing-completos, 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 debería aparecer en una página web; no especifica el flujo de control para representar una página ni las posibles interacciones de la página con un usuario.
A partir de 2013, algunos sistemas de software 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 de servidor back-end para admitir la interfaz declarada. Dichos sistemas, que suelen utilizar un espacio de nombres XML específico del 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.
Programación funcional
Los lenguajes de programación funcional como Haskell, Scheme y ML evalúan expresiones a través de la 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. Por ejemplo, en Scheme, el orden de evaluación de muchos tipos de subexpresiones no está definido o es implícito. 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 mapeos 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 de estado en los programas.
Lenguajes híbridos
Los Makefiles, por ejemplo, especifican dependencias de manera declarativa, 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 idioma anfitrión, lo que suele ser imperativo (como C).
Programación lógica
Lenguajes de programación lógica como el estado de Prolog y las relaciones de consulta. Los detalles de cómo se responden estas consultas dependen de la implementación y su probador de teoremas, pero generalmente toman la forma de algún tipo de unificación. Al igual que la programación funcional, muchos lenguajes de programación lógica permiten efectos secundarios y, como resultado, no son estrictamente declarativos.
Modelado
Los modelos, o representaciones matemáticas, de sistemas físicos pueden implementarse en un código informático 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 se impone típicamente en los límites del sistema físico, mientras que la descripción conductual del sistema mismo es declarativa o acausal. Los lenguajes y entornos de modelado declarativo incluyen Analytica, Modelica y Simile.
Ejemplos
Ceceo
Lisp (1958) significa "LISt Processor." Se adapta a las listas de procesos. Una estructura de datos se forma construyendo listas de listas. En la memoria, esto forma una estructura de datos de árbol. Internamente, la estructura de árbol de los datos típicos de Lisp se presta muy bien al procesamiento con funciones recursivas. La sintaxis para construir un árbol es encerrar los elementos separados por espacios en blanco entre paréntesis. La siguiente es una lista de tres elementos. Los primeros dos elementos son en sí mismos listas de dos elementos cada uno:
()A B) ()HELLO WORLD) 94)
Lisp tiene funciones para extraer y reconstruir elementos. La función car
(a veces llamada first
) devuelve el primer elemento de la lista. La función cdr
(a veces llamada rest
) devuelve una lista que contiene todo excepto el primer elemento. La función cons
devuelve una lista que es el segundo argumento con el primero antepuesto. Por lo tanto, si X no es una lista vacía, la siguiente expresión se evalúa como la lista X
:
()cons ()coche x) ()cdr x)
El propio código devuelve una copia de la lista X
. Como es típico en los lenguajes funcionales, las operaciones en Lisp a menudo copian datos cuando se les pide que produzcan datos nuevos a partir de datos antiguos. La estructura de árbol de los datos de Lisp también facilita esto: una nueva estructura hecha de datos preexistentes comparte la mayor parte posible de su estructura interna con sus antecedentes, con nuevas adiciones almacenadas como ramas del árbol y referencias a la estructura original bajo su nombre original devolverá exactamente eso y nada más.
Un inconveniente de Lisp es que cuando se anidan muchas funciones, los paréntesis pueden parecer confusos. Los entornos modernos de Lisp ayudan a garantizar la coincidencia de paréntesis. Aparte, Lisp admite las operaciones de lenguaje imperativo de la declaración de asignación y los bucles goto. Además, Lisp no se preocupa por el tipo de datos de los elementos en tiempo de compilación. En su lugar, asigna los tipos de datos en tiempo de ejecución. Esto puede llevar a que los errores de programación no se detecten al principio del proceso de desarrollo. Para contrarrestar esto, el desarrollo de Lisp generalmente se lleva a cabo de una manera extremadamente incremental, con funciones y funciones de orden superior creadas y probadas en vivo durante el desarrollo. Además, mediante el uso de macros, que son funciones de Lisp que operan sobre los programas de Lisp como estructuras de datos, la verificación de tipos se puede realizar discrecionalmente en cualquier punto que desee el programador.
Escribir programas Lisp grandes, fiables y legibles requiere previsión. Si se planifica adecuadamente, el programa puede ser mucho más breve que un programa equivalente en lenguaje imperativo. Lisp es ampliamente utilizado en inteligencia artificial. Sin embargo, su uso ha sido aceptado solo porque tiene operaciones de lenguaje imperativo, lo que hace posibles los efectos secundarios no deseados.
ML
ML (1973) significa "Metalenguaje". ML tiene tipos estáticos y los argumentos de función y los tipos de retorno pueden anotarse.
diversión times_10()n : int) : int = 10 * n;
ML no está tan centrado en paréntesis como Lisp y, en su lugar, utiliza una variedad más amplia de sintaxis para codificar la relación entre los elementos del código, en lugar de apelar a la ordenación de listas y anidando para expresarlo todo. La siguiente es una aplicación de times_10
:
times_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.
Prólogo
Prolog (1972) significa "Programación en LOGic." Fue diseñado para procesar lenguajes naturales. Los componentes básicos de un programa Prolog son objetos y sus relaciones con otros objetos. Los objetos se construyen declarando hechos verdaderos sobre ellos.
Los hechos de la teoría de conjuntos se forman asignando objetos a conjuntos. La sintaxis es setName(objeto).
- El gato es un animal.
animal(cat).
- El ratón es un animal.
animal(mouse).
- Tom es un gato.
cat(tom).
- Jerry es un ratón.
mouse(jerry).
Los adjetivos hechos se forman usando adjetivo(objeto).
- El gato es grande.
big(cat).
- El ratón es pequeño.
small(mouse).
Las relaciones se forman utilizando varios elementos dentro de los paréntesis. En nuestro ejemplo tenemos verbo(objeto,objeto)
y verbo(adjetivo,adjetivo)
.
- El ratón come queso.
eat(mouse,cheese).
- Los animales grandes comen animales pequeños.
eat(big,small).
Después de ingresar todos los hechos y relaciones, se puede hacer una pregunta:
- ¿Tom comerá Jerry?
?- eat(tom,jerry).
El uso de Prolog se ha ampliado para convertirse en un lenguaje orientado a objetivos. En una aplicación orientada a objetivos, el objetivo se define proporcionando una lista de subobjetivos. Luego, cada subobjetivo se define proporcionando además una lista de sus subobjetivos, etc. Si un camino de subobjetivos no logra encontrar una solución, entonces ese subobjetivo se retrocede y se intenta sistemáticamente otro camino. Las aplicaciones prácticas incluyen resolver el problema del camino más corto y producir árboles genealógicos.
Contenido relacionado
WinNuke
ABC 80
UNIVAC 1103