¿Por qué no se ha cumplido el sueño de la programación declarativa? ¿Cuáles son algunos obstáculos concretos que se interponen en el camino? Por un simple ejemplo, ¿por qué no puedo decir
sort(A) is defined by sort(A) in perm(A) && asc(sort(A))
y automáticamente obtiene un algoritmo de clasificación. perm
significa permutaciones y asc
significa ascendente.
declarative-programming
davidk01
fuente
fuente
n!
permutaciones de una secuencia y, en el peor de los casos, su algoritmo tendrá que probarlas todas para encontrar una ordenada. El tiempo factorial es tan malo como lo puede hacer un algoritmo para procesar una secuencia.Respuestas:
Hay algunas muy buenas respuestas. Intentaré contribuir a la discusión.
Sobre el tema de la programación declarativa y lógica en Prolog, está el gran libro "The Craft of Prolog" de Richard O'Keefe . Se trata de escribir programas eficientes utilizando un lenguaje de programación que le permite escribir programas muy ineficientes. En este libro, mientras discute las implementaciones eficientes de varios algoritmos (en el capítulo "Métodos de programación"), el autor adopta el siguiente enfoque:
La observación más esclarecedora (para mí) que pude hacer mientras trabajaba a través de estos:
Sí, la versión final de la implementación es mucho más eficiente que la especificación "declarativa" con la que comenzó el autor. Todavía es muy declarativo, sucinto y fácil de entender. Lo que sucedió en el medio es que la solución final captura las propiedades del problema al que la solución inicial era ajena.
En otras palabras, al implementar una solución, hemos utilizado tanto de nuestro conocimiento sobre el problema como hemos podido. Comparar:
a:
Un pequeño aparte: una definición como la que has dado es atractiva porque es muy general. Sin embargo, no puedo escapar de la sensación de que ignora deliberadamente el hecho de que las permutaciones son, bueno, un problema combinatorio. ¡Esto es algo que ya sabemos ! Esto no es una crítica, solo una observación.
En cuanto a la verdadera pregunta: ¿cómo avanzar? Bueno, una forma es proporcionar tanto conocimiento sobre el problema que estamos declarando a la computadora.
El mejor intento que conozco para resolver realmente el problema se presenta en los libros escritos por Alexander Stepanov, "Elementos de programación" y "De las matemáticas a la programación genérica" . Lamentablemente, no estoy a la altura de la tarea de resumir (o incluso comprender completamente) todo en estos libros. Sin embargo, el enfoque es definir algoritmos de biblioteca y estructuras de datos eficientes (o incluso óptimos), con la condición de que todas las propiedades relevantes de la entrada se conozcan de antemano. El resultado final es:
En cuanto a por qué todavía no ha sucedido, bueno, la informática es un campo muy joven, y todavía estamos haciendo frente a apreciar realmente la novedad de la mayoría.
PD
Para darle una idea de lo que quiero decir con "refinar la implementación": tomemos, por ejemplo, el problema fácil de obtener el último elemento de una lista, en Prolog. La solución declarativa canónica es decir:
Aquí, el significado declarativo de
append/3
es:Dado que en el segundo argumento
append/3
tenemos una lista con solo un elemento, y el primer argumento es ignorado (el guión bajo), obtenemos una división de la lista original que descarta el frente de la lista (List1
en el contexto deappend/3
) y exige que la parte posterior (List2
en el contexto deappend/3
) es de hecho una lista con un solo elemento: por lo tanto, es el último elemento.La implementación real proporcionada por SWI-Prolog , sin embargo, dice:
Esto sigue siendo muy bien declarativo. Lea de arriba a abajo, dice:
La razón por la que se proporciona esta implementación es para solucionar los problemas prácticos que rodean el modelo de ejecución de Prolog. Idealmente, no debería marcar la diferencia qué implementación se utiliza. Del mismo modo, podríamos haber dicho:
Si desea llenar sus discusiones inconclusas sobre lo que es bueno, el Prolog declarativo, simplemente revise algunas de las preguntas y respuestas en la etiqueta Prolog en Stack Overflow .
fuente
Los lenguajes lógicos ya hacen esto. Puede definir la ordenación de forma similar a como lo está haciendo.
El principal problema es el rendimiento. Las computadoras pueden ser excelentes para calcular muchas cosas, pero son inherentemente tontas. Cada decisión "inteligente" que una computadora podría tomar fue programada por un programador. Y esta decisión generalmente se describe no por cómo se ve el resultado final, sino por cómo lograr, paso a paso, este resultado final.
Imagina la historia de un Golem . Si intentas darle un comando abstracto, en el mejor de los casos, lo hará de manera ineficiente y, en el peor de los casos, se lastimará a ti mismo, a ti oa alguien más. Pero si describe lo que desea con el mayor detalle posible, tiene la garantía de que la tarea se completará de manera efectiva y eficiente.
El trabajo del programador es decidir qué nivel de abstracción usar. Para la aplicación que está haciendo, ¿va a ir a un nivel superior y describirlo de manera abstracta y tomar el rendimiento o ir bajo y sucio, pasar 10 veces más tiempo en él, pero obtener un algoritmo que es 1000 veces más eficaz?
fuente
Además del excelente punto de Euphoric , me gustaría agregar que ya estamos usando lenguajes declarativos en muchos lugares donde funcionan bien, es decir, describir un estado que no es probable que cambie o solicitar algo para lo cual la computadora realmente puede generar código eficiente por sí mismo:
HTML declara cuál es el contenido de una página web.
CSS declara cómo deberían ser los distintos tipos de elementos en una página web.
Cada base de datos relacional tiene un lenguaje de definición de datos que declara cuál es la estructura de la base de datos.
SQL está mucho más cerca de lo declarativo que de lo imperativo, ya que usted le dice lo que quiere ver y el planificador de consultas de la base de datos descubre cómo hacerlo realidad.
Se podría argumentar que la mayoría de los archivos de configuración (.vimrc, .profile, .bashrc, .gitconfig) están utilizando un lenguaje específico de dominio que es en gran parte declarativo
fuente
Las abstracciones tienen fugas
Puede implementar un sistema declarativo donde declare lo que desea, y el compilador o el intérprete descifran un orden de ejecución. El beneficio teórico es que te libera de tener que pensar en el "cómo" y no tienes que detallar esta implementación. Sin embargo, en la práctica para la informática de propósito general, aún debe pensar en el 'cómo' y escribir todo tipo de trucos, teniendo en cuenta cómo se implementará, ya que de lo contrario el compilador puede (y a menudo elegirá) una implementación que será muy, muy, muy lento (por ejemplo, n! operaciones donde n sería suficiente).
En su ejemplo particular, obtendrá un algoritmo de clasificación A , no significa que obtendrá uno bueno o incluso algo utilizable. Su definición dada, si se implementa literalmente (como probablemente lo haría un compilador) da como resultado http://en.wikipedia.org/wiki/Bogosort, que no se puede usar para conjuntos de datos más grandes: es técnicamente correcto, pero necesita una eternidad para ordenar mil números .
Para algunos dominios limitados, puede escribir sistemas que casi siempre funcionan bien para descubrir una buena implementación, por ejemplo, SQL. Para la informática de propósito general que no funciona particularmente bien: puede escribir sistemas en, por ejemplo, Prolog, pero tiene que visualizar cómo exactamente sus declaraciones se convertirán en un orden de ejecución imperativo al final, y eso pierde gran parte de la declaración declarada Beneficios de programación.
fuente
La capacidad de decisión computacional es la razón más importante por la que la programación declarativa no ha resultado ser tan fácil como parece.
Muchos problemas que son relativamente fáciles de enunciar han demostrado ser indecidibles o tienen una complejidad NP completa para resolver. Esto ocurre a menudo cuando tenemos en cuenta las clases negativas y la clasificación, la contabilidad y la recursividad.
Me gustaría ejemplificar esto con algunos dominios que son bien conocidos.
La decisión sobre qué clase de CSS usar necesita conocimiento y consideración de todas las reglas de CSS. Agregar nuevas reglas podría invalidar todas las demás decisiones. Las clases negativas de CSS no se agregan intencionalmente al lenguaje, debido a problemas de NP completo, pero la falta de clases negativas complica las decisiones de diseño de CSS.
Dentro de un optimizador de consultas (SQL), existe el problemático problema de decidir en qué orden unirse, qué índices usar y qué memoria asignar a qué resultados temporales. Este es un problema conocido de NP completo y complica el diseño de la base de datos y la formulación de consultas. Para formularlo de manera diferente: al diseñar una base de datos o una consulta, el diseñador necesita conocer las acciones y el orden de las acciones que probablemente tomará el optimizador de consultas. Un ingeniero experimentado necesita conocimiento de la heurística utilizada por los principales proveedores de bases de datos.
Los archivos de configuración son declarativos, pero ciertas configuraciones son difíciles de declarar. Por ejemplo, para configurar adecuadamente las características, uno debe tener en cuenta el control de versiones, la implementación (y el historial de implementación), las posibles anulaciones manuales y los posibles conflictos con otras configuraciones. Validar adecuadamente una configuración puede convertirse en un problema NP-completo.
El resultado es que estas complicaciones toman por sorpresa a los principiantes, rompen la 'belleza' de la programación declarativa y hacen que algunos ingenieros busquen otras soluciones. La migración de ingenieros sin experiencia de SQL a NoSQL podría haber sido provocada por las complejidades subyacentes de las bases de datos relacionales.
fuente
Tenemos una diferencia en la declarativa de los lenguajes de programación que se usa bien en la verificación de la lógica digital.
Normalmente, la lógica digital se describe en el nivel de transferencia de registro (RTL) donde se define el nivel lógico de las señales entre registros. Para comprobar que estamos agregando cada vez más propiedades definidas de una manera más abstracta y declarativa.
Uno de los subconjuntos de idiomas / idiomas más declarativos se llama PSL para el lenguaje de especificación de propiedades. Al probar un modelo RTL de un multiplicador en el que, por ejemplo, se deben especificar todas las operaciones lógicas de cambio y suma en múltiples ciclos de reloj; puede escribir una propiedad de la manera de
assert that when enable is high, this output will equal the multiplication of these two inputs after no more than 8 clock cycles
. La descripción de la PSL se puede verificar junto con la RTL en una simulación, o se puede demostrar formalmente que la PSL es válida para la descripción de la RTL.El modelo PSL más declarativo obliga a uno a describir el mismo comportamiento que la descripción RTL, pero de una manera suficientemente diferente que puede verificarse automáticamente contra el RTL para ver si están de acuerdo.
fuente
Principalmente el problema es cómo modela los datos; y la programación declarativa no está ayudando aquí. En los idiomas imperativos ya tienes toneladas de bibliotecas que hacen muchas cosas por ti, por lo que solo necesitas saber a qué llamar. De una manera particular, uno podría considerar esta programación declarativa (probablemente el mejor ejemplo para esto es Stream API en Java 8 ). Teniendo esto, la abstracción ya está resuelta y la programación declarativa no es necesaria.
Además, como se ha dicho, los lenguajes de programación lógica ya hacen exactamente lo que quieres. Se podría decir que el problema es el rendimiento, pero con el hardware y la investigación actuales en esta área, las cosas pueden mejorarse para estar listas para su uso en producción; en realidad, Prolog se usa para cosas de IA, pero creo que solo la academia.
Cabe señalar que se aplica a los lenguajes de programación de uso general. Para lenguajes específicos de dominio, los lenguajes declarativos son mucho mejores; SQL probablemente es el mejor ejemplo.
fuente
Se vería así: {(lo que sea => leer un archivo y llamar a una url) | llamar a una url y leer un archivo} Sin embargo, estas son acciones para ejecutar, y el estado del sistema cambia como resultado, pero eso no es obvio desde el origen.
Los declarativos pueden describir una máquina de estados finitos y sus transiciones. El FSM es como lo opuesto a los declarativos sin acciones, incluso si la única acción es cambiar el estado al siguiente estado.
La ventaja de usar este método es que las transiciones y acciones pueden especificarse mediante predicados que se aplican a múltiples transiciones, en lugar de solo una.
Sé que esto suena un poco extraño, pero en 2008 escribí un generador de programas que usa este método, y el C ++ generado es de 2 a 15 veces más que la fuente. Ahora tengo más de 75,000 líneas de C ++ de 20,000 líneas de entrada. Dos cosas van con esto: consistencia e integridad.
Consistencia: no hay dos predicados que puedan ser verdaderos al mismo tiempo que impliquen acciones inconsistentes, ya que tanto x = 8 como x = 9, ni estados próximos diferentes.
Integridad: se especifica la lógica para cada transición de estado. Estos pueden ser difíciles de verificar para sistemas con N subestados, con> 2 ** N estados, pero existen métodos combinatorios interesantes que pueden verificar todo. En 1962 escribí la fase 1 de un sistema para las máquinas 7070, usando este tipo de generación de código condicional y depuración combinatoria. De las 8,000 líneas en el género, ¡el número de errores desde el día del primer lanzamiento para siempre fue cero!
La fase dos del tipo, 12,000 líneas, tuvo más de 60 errores en los primeros dos meses. Hay mucho más que decir sobre esto, pero funciona. Si los fabricantes de automóviles usaran este método para verificar el firmware, no veríamos las fallas que vemos ahora.
fuente
No todo se puede representar de manera declarativa.
A menudo, usted explícitamente desea para controlar el flujo de ejecución
Por ejemplo, siguiendo el pseudocódigo:
if whatever read a file call an URL else call an URL write a file
¿Cómo lo representaría declarativamente?Claro, probablemente hay algunas formas exóticas de hacerlo. Como mónadas . Pero estos suelen ser más engorrosos, complicados y mucho menos intuitivos que su parte procesal.
Todo se reduce al hecho de que "interactuar" con su entorno / sistema no es declarativo. Todo lo relacionado con E / S es procesal por esencia. Tiene que decir exactamente cuándo y qué debe suceder, y en qué orden.
Declarative es excelente para todo lo que está puramente relacionado con la computación. Como una función gigante, pones X y obtienes Y. Eso es genial. Un ejemplo de esto es HTML, la entrada es texto, la salida es lo que ve en su navegador.
fuente
if
/else
, en cuyo caso, cómo sería una alternativa declarativa? Ciertamente no son las partesread
/write
/call
, ya que son buenas listas declarativas de valores (si estás insinuando que están envueltas{...; ...}
, ¿por qué no implica que están envueltas[..., ...]
?) Por supuesto, la lista son solo monoides libres; muchos otros lo harían también. No veo por qué las mónadas son relevantes aquí; son solo una API. Haskell pasó de secuencias -> mónadas para ayudar a la composición manual, pero los lenguajes lógicos pueden componer listas en orden automáticamente.