Lenguaje de programación

Un lenguaje de programación  es un lenguaje formal diseñado para escribir programas de computadora [1] [2] . Un lenguaje de programación define un conjunto de reglas léxicas , sintácticas y semánticas que determinan la apariencia del programa y las acciones que el ejecutante (generalmente una computadora ) realizará bajo su control.

Desde la creación de las primeras máquinas programables, la humanidad ha ideado más de ocho mil lenguajes de programación (entre ellos esotérico , visual y de juguete ) [3] . Cada año su número aumenta. Algunos lenguajes son utilizados solo por un pequeño número de sus propios desarrolladores, otros son conocidos por millones de personas. Los programadores profesionales pueden dominar varios lenguajes de programación.

Un lenguaje de programación está diseñado para escribir programas de computadora, que son un conjunto de reglas que le permiten a una computadora realizar un proceso computacional particular , organizar el manejo de varios objetos, etc. Un lenguaje de programación se diferencia de los lenguajes naturales en que está diseñado para controlar una computadora, mientras que los lenguajes naturales se utilizan principalmente para la comunicación entre personas. La mayoría de los lenguajes de programación usan construcciones especiales para definir y manipular estructuras de datos y controlar el proceso de computación.

Como regla general, un lenguaje de programación se define no solo a través de las especificaciones del lenguaje estándar , que definen formalmente su sintaxis y semántica , sino también a través de las encarnaciones (implementaciones) del estándar  : herramientas de software que proporcionan traducción o interpretación de programas en este idioma ; dichas herramientas de software difieren según el fabricante, la marca y la variante (versión), el momento del lanzamiento, la integridad de la implementación del estándar, las características adicionales; puede tener ciertos errores o características de implementación que afectan la práctica de usar el lenguaje o incluso su estándar.

Historia

Primeras etapas de desarrollo

Podemos decir que los primeros lenguajes de programación surgieron incluso antes del advenimiento de las computadoras electrónicas modernas: ya en el siglo XIX , se inventaron dispositivos que pueden llamarse programables con cierto grado de convención, por ejemplo, una caja de música (y más tarde un piano mecánico ) utilizando un cilindro de metal y un telar Jacquard (1804) mediante cartulinas. Para controlarlos, se utilizaron conjuntos de instrucciones que, en el marco de la clasificación moderna, pueden considerarse prototipos de lenguajes de programación específicos de dominio . Significativo puede considerarse el "lenguaje" en el que Lady Ada Augusta (Condesa de Lovelace) en 1842 escribió un programa para calcular los números de Bernoulli para el motor analítico de Charles Babbage , que, de implementarse, se convertiría en la primera computadora del mundo, aunque mecánico - con una máquina de vapor.

En 1930 - 1940, A. Church , A. Turing , A. Markov desarrollaron abstracciones matemáticas ( cálculo lambda , máquina de Turing , algoritmos normales , respectivamente) para formalizar algoritmos .

Al mismo tiempo, en la década de 1940, aparecieron las computadoras digitales eléctricas y se desarrolló un lenguaje que puede considerarse el primer lenguaje de programación de computadoras de alto nivel - " Plankalkül ", creado por el ingeniero alemán K. Zuse en el período de 1943 a 1945 [4] .

Los programadores informáticos de principios de la década de 1950 , especialmente como UNIVAC e IBM 701, utilizaron directamente el código de máquina al crear programas , el registro del programa en el que constaba de unos y ceros y que se considera el lenguaje de programación de la primera generación (mientras que diferentes máquinas de diferentes fabricantes usaban códigos diferentes, lo que requería reescribir el programa al cambiar a otra computadora).

El primer lenguaje implementado prácticamente fue en 1949 el llamado " Código Corto ", en el que las operaciones y variables se codificaban con combinaciones de dos caracteres. Fue desarrollado por Eckert-Mauchly Computer Corporation , que produjo UNIVAC, creado por uno de los empleados de Turing, John Mauchly . Mauchly instruyó a su personal para que desarrollara un traductor de fórmulas matemáticas, pero para la década de 1940 este objetivo era demasiado ambicioso. El código corto se implementó utilizando el intérprete [5] .

Pronto, este método de programación fue reemplazado por el uso de lenguajes de segunda generación, también limitados por las especificaciones de máquinas específicas , pero más fáciles para el uso humano debido al uso de mnemónicos (notación simbólica para instrucciones de máquina) y la capacidad de asignar nombres a direcciones en la memoria de la máquina. Son tradicionalmente conocidos como lenguajes ensambladores y autocódigos . Sin embargo, al utilizar un ensamblador, se hizo necesario traducir el programa a código máquina antes de ejecutarlo, para lo cual se desarrollaron programas especiales, también llamados ensambladores. También hubo problemas con la portabilidad de un programa de una computadora de una arquitectura a otra, y la necesidad de que un programador, al resolver un problema, pensara en términos de un "nivel bajo": una celda, una dirección, un comando. . Posteriormente, se mejoraron los lenguajes de segunda generación para incluir soporte para macros .

A partir de mediados de la década de 1950 comenzaron a aparecer lenguajes de tercera generación como Fortran , Lisp y Cobol [6] . Los lenguajes de programación de este tipo son más abstractos (también se les llama "lenguajes de alto nivel") y universales, no tienen una dependencia rígida de una plataforma de hardware específica y las instrucciones de la máquina que se utilizan en ella. Un programa en un lenguaje de alto nivel se puede ejecutar (al menos en teoría, en la práctica suele haber una serie de versiones o dialectos específicos de la implementación del lenguaje) en cualquier ordenador que disponga de un traductor para este lenguaje (herramienta que traduce el programa al lenguaje de máquina, después de lo cual puede ser ejecutado por el procesador).

Las versiones actualizadas de estos lenguajes todavía están en circulación en el desarrollo de software, y cada uno de ellos tuvo un cierto impacto en el desarrollo posterior de los lenguajes de programación [7] . Luego, a fines de la década de 1950, apareció Algol , que también sirvió como base para una serie de desarrollos posteriores en esta área. Cabe señalar que el formato y el uso de los primeros lenguajes de programación estaban muy influenciados por las limitaciones de la interfaz [8] .

Mejora

Durante las décadas de 1960 y 1970 se desarrollaron los  principales paradigmas de los lenguajes de programación utilizados en la actualidad, aunque en muchos aspectos este proceso fue solo una mejora sobre las ideas y conceptos establecidos en los primeros lenguajes de tercera generación.

Cada uno de estos lenguajes generó una familia de descendientes, y la mayoría de los lenguajes de programación modernos se basan en última instancia en uno de ellos.

Además, en las décadas de 1960 y 1970, hubo un debate activo sobre la necesidad de soportar la programación estructurada en ciertos lenguajes [14] . En particular, el especialista holandés E. Dijkstra habló en forma impresa con propuestas para un rechazo total del uso de instrucciones GOTO en todos los lenguajes de alto nivel. También se desarrollaron técnicas encaminadas a reducir el volumen de programas y aumentar la productividad del programador y del usuario.

Consolidación y desarrollo

En la década de 1980, comenzó un período que puede llamarse condicionalmente el momento de la consolidación. El lenguaje C ++ combinó las características de la programación orientada a objetos y de sistemas, el gobierno de EE . UU . estandarizó el lenguaje Ada , derivado de Pascal y diseñado para su uso en sistemas de control a bordo para instalaciones militares, se realizaron inversiones significativas en Japón y otros países del mundo para estudiar las perspectivas de los llamados lenguajes de quinta generación, que incluirían construcciones de programación lógica [15] . La comunidad de lenguaje funcional ha adoptado ML y Lisp como estándar. En general, este período se caracterizó más por construir sobre las bases establecidas en la década anterior que por desarrollar nuevos paradigmas.

Una tendencia importante que se ha observado en el desarrollo de lenguajes de programación para sistemas a gran escala ha sido el enfoque en el uso de módulos - unidades volumétricas de organización de código. Aunque algunos lenguajes, como PL/1, ya admitían la funcionalidad correspondiente, el sistema modular también se abrió paso en los lenguajes Modula-2 , Oberon , Ada y ML. A menudo, los sistemas modulares se combinaron con construcciones de programación genéricas [16] .

Los lenguajes de programación visuales (gráficos) se están convirtiendo en un área importante de trabajo , en la que el proceso de “escribir” un programa como texto se reemplaza por el proceso de “dibujar” (diseñar un programa en forma de diagrama) en una pantalla de computadora. Los lenguajes visuales brindan visibilidad y una mejor percepción de la lógica del programa por parte de una persona.

En la década de 1990, en relación con el desarrollo activo de Internet , se generalizaron los lenguajes que le permiten crear secuencias de comandos de páginas web  , principalmente Perl , que se desarrolló a partir de una herramienta de secuencias de comandos para sistemas Unix, y Java . La popularidad de las tecnologías de virtualización también aumentó . Estos cambios, sin embargo, tampoco representaron innovaciones fundamentales, sino más bien una mejora sobre paradigmas y lenguajes ya existentes (en este último caso, principalmente la familia C).

Actualmente, el desarrollo de los lenguajes de programación va en la dirección de aumentar la seguridad y confiabilidad, creando nuevas formas de organización de códigos modulares e integración con bases de datos .

Especificación de idioma

Estandarización

Se han creado estándares internacionales para muchos lenguajes de programación ampliamente utilizados . Organizaciones especiales actualizan y publican periódicamente especificaciones y definiciones formales del idioma correspondiente. En el marco de dichos comités, continúa el desarrollo y la modernización de los lenguajes de programación y se resuelven problemas sobre la expansión o el soporte de construcciones de lenguaje nuevas y existentes.

Alfabeto

Los lenguajes de programación modernos están diseñados para usar ASCII , es decir, la disponibilidad de todos los caracteres gráficos ASCII es una condición necesaria y suficiente para escribir cualquier construcción de lenguaje. Los caracteres de control ASCII se utilizan de forma limitada: solo se permiten el retorno de carro CR, el avance de línea LF y la tabulación horizontal HT (a veces también la tabulación vertical VT y la página siguiente FF).

Los primeros lenguajes, que surgieron durante la era de los caracteres de 6 bits , usaban un conjunto más limitado. Por ejemplo, el alfabeto Fortran tiene 49 caracteres (incluido el espacio): ABCDEFGHIJKLMNOPQRSTU VWXYZ 0 1 2 3 4 5 6 7 8 9 = + - * / () . ps

Una excepción notable es el lenguaje APL , que usa muchos caracteres especiales.

El uso de caracteres que no son ASCII (como los caracteres KOI8-R o los caracteres Unicode ) depende de la implementación: a veces solo se permiten en comentarios y constantes de caracteres/cadenas, y a veces también se permiten en identificadores. En la URSS , había idiomas en los que todas las palabras clave estaban escritas en letras rusas, pero tales idiomas no mucha popularidad (la excepción es el lenguaje de programación incorporado 1C: Enterprise ).

La expansión del juego de caracteres utilizado está limitada por el hecho de que muchos proyectos de desarrollo de software son internacionales. Sería muy difícil trabajar con un código donde los nombres de algunas variables están escritos en letras rusas, otras en árabe y otras en caracteres chinos. Al mismo tiempo, los lenguajes de programación de nueva generación ( Delphi 2006 , C# , Java ) admiten Unicode para trabajar con datos de texto .

Gramática

Semántica

Hay varios enfoques para definir la semántica de los lenguajes de programación. Hay tres principales: operacionales , axiomáticas y denotacionales .

Clasificación

No existe una taxonomía sistemática generalmente aceptada de lenguajes de programación. Hay muchas características según las cuales es posible clasificar los idiomas, y algunos de ellos trazan inequívocamente divisiones entre idiomas sobre la base de propiedades técnicas, otros se basan en características dominantes, tienen excepciones y son más condicionales, y otros son completamente subjetivos y, a menudo, acompañados de conceptos erróneos, pero en la práctica son muy comunes.

Un lenguaje de programación particular en la gran mayoría de los casos tiene más de un lenguaje antepasado. Muchos idiomas se crean como una combinación de elementos de diferentes idiomas. En algunos casos, dicha combinación se somete a un análisis matemático de coherencia (consulte, por ejemplo, la definición estándar de ML ), en otros, el lenguaje se forma en función de las necesidades prácticas, para resolver problemas reales con el fin de obtener el éxito comercial, pero sin observar las matemáticas. rigor y con la inclusión de ideas mutuamente excluyentes en el lenguaje (como en el caso de C++ [17] [18] [19] [20] [21] ).

Lenguajes de bajo y alto nivel

Por lo general, "nivel de idioma" se refiere a:

Esta dualidad apareció en la década de 1950 , con la creación de las lenguas Plankalkül y Fortran . Durante su desarrollo, se establecieron intenciones directas para proporcionar un registro más conciso de construcciones encontradas con frecuencia (por ejemplo, expresiones aritméticas) que lo que requerían los procesadores de esa época. Estos lenguajes introdujeron una nueva capa de abstracción y se suponía que transformaban los programas en lenguaje de máquina , por lo que se les llamó lenguajes de "alto nivel", es decir, una superestructura, una capa por encima del lenguaje de máquina. Sin embargo, pronto quedó claro que estas definiciones no necesariamente van juntas. Por lo tanto, la historia conoce casos en los que un lenguaje tradicionalmente considerado de "alto nivel" se implementó en hardware (ver Lisp Machine , Java Optimized Processor ), o cuando un lenguaje que es de "bajo nivel" en una plataforma se compiló como " de alto nivel" en otro (por lo tanto , los programas ensambladores VAX CISC se usaron en máquinas DEC Alpha RISC  ; consulte VAX Macro ). Así, el concepto de nivel lingüístico no es estrictamente formal, sino condicional.

Los lenguajes de bajo nivel incluyen, en primer lugar, lenguajes de máquina (o, en la jerga común, códigos de máquina), es decir, lenguajes implementados directamente a nivel de hardware. Pertenecen a la primera generación de lenguajes de programación . Poco después de ellos, aparecieron los lenguajes de segunda generación  , los llamados " lenguajes ensambladores ". En el caso más simple, implementan un mnemotécnico de lenguaje máquina para escribir comandos y sus parámetros (en particular, direcciones en memoria). Además, muchos lenguajes ensambladores incluyen un lenguaje de macros muy desarrollado . Los lenguajes de primera y segunda generación le permiten controlar con precisión cómo se ejecutará la funcionalidad requerida en un procesador determinado, teniendo en cuenta las características de su arquitectura. Por un lado, esto asegura un alto rendimiento y compacidad de los programas, pero por otro lado, para transferir un programa a otra plataforma de hardware, es necesario recodificarlo (y, a menudo, rediseñarlo debido a las diferencias en la arquitectura del procesador) desde cero. La mayoría de los lenguajes ensambladores no están tipificados , pero también hay lenguajes ensambladores tipificados , destinados a proporcionar una seguridad mínima para programas de bajo nivel.

Para la década de 1970, la complejidad de los programas había crecido tanto que excedía la capacidad de los programadores para manejarlos, y esto condujo a enormes pérdidas y estancamiento en el desarrollo de la tecnología de la información [22] . La respuesta a este problema ha sido la aparición de una masa de lenguajes de alto nivel que ofrecen una variedad de formas de gestionar la complejidad (para más detalles, consulte el paradigma de programación y los lenguajes para programar a pequeña y gran escala ). Los programas en lenguajes de "alto nivel" son mucho más fáciles de modificar y muy fáciles de transferir de una computadora a otra. En la práctica, los lenguajes de tercera generación , que solo pretenden ser de "alto nivel", pero en realidad proporcionan solo aquellas construcciones de "alto nivel" que encuentran una correspondencia uno a uno con las instrucciones en la máquina de von Neumann [23] han recibido el uso más generalizado .

Los lenguajes de la cuarta generación incluyen lenguajes de orden superior . A veces se distingue una categoría de lenguajes de quinta generación, pero generalmente no se acepta: el término " lenguaje de muy alto nivel " se usa con más frecuencia . Son lenguajes cuya implementación incluye un importante componente algorítmico (es decir, donde la interpretación de un código fuente pequeño requiere cálculos muy complejos). En la mayoría de los casos, esto se denomina lenguajes lógicos , que también se dice que son solo lenguajes de cuarta generación, complementados con una base de conocimientos [24] . Además, los "lenguajes de alto nivel" incluyen lenguajes visuales y lenguajes basados ​​en un subconjunto del lenguaje natural (por ejemplo, la llamada "prosa comercial").  

Una categoría importante son los lenguajes específicos de dominio ( DSL - Domain Specific Language ) .  La asignación de un idioma a esta categoría es muy arbitraria y, a menudo, controvertida; en la práctica, este término se puede aplicar a representantes de la tercera, cuarta y quinta generación de idiomas. A veces incluso clasifican el lenguaje C , que se puede atribuir a la generación "2.5". Originalmente se comercializó como "ensamblador de alto nivel"; a menudo también se lo conoce como un "lenguaje de nivel intermedio". Le permite controlar en gran medida la forma en que se implementa el algoritmo, teniendo en cuenta las propiedades típicas de una gran cantidad de arquitecturas de hardware. Sin embargo, hay plataformas para las que no existen implementaciones de C (incluso en una forma no estándar) debido a la imposibilidad o inconveniencia fundamental de su creación. Con el tiempo, aparecieron otros lenguajes de nivel medio, como LLVM , C-- .

Las tres primeras generaciones de lenguajes forman un paradigma de programación imperativo , y las generaciones posteriores forman uno declarativo [24] . El término " imperativo " significa "orden de mando", es decir, programación por medio de instrucciones paso a paso para la máquina, o una indicación detallada de la forma que el programador ya ha inventado para implementar la tarea técnica. El término " declarativo " significa "descripción", es decir, programación al proporcionar una formalización de los términos de referencia en una forma adecuada para transformaciones automáticas , con libertad de elección para el traductor de idiomas . Los lenguajes imperativos tienen como objetivo describir cómo obtener un resultado, mientras que los lenguajes de nivel superior tienen como objetivo describir qué se requiere como resultado. Por lo tanto, los primeros se denominan as -languages ​​(o lenguajes orientados a máquinas), y los segundos se denominan what -languages ​​(o lenguajes orientados a humanos). Para muchos problemas, una generación completamente automática de una implementación verdaderamente eficiente es algorítmicamente indecidible , por lo que en la práctica, incluso en qué lenguajes, a menudo se usan ciertos trucos algorítmicos. Sin embargo, existen métodos para obtener implementaciones eficientes basadas en definiciones (implementaciones frontales), como la supercompilación inventada en la URSS .

En la mayoría de los casos, los lenguajes de alto nivel producen un código de máquina más grande y se ejecutan más lentamente. Sin embargo, algunos lenguajes de alto nivel para programas complejos desde el punto de vista algorítmico y estructural pueden proporcionar una ventaja notable en eficiencia, cediendo ante los de bajo nivel solo en programas pequeños y simples (ver eficiencia del lenguaje para más detalles ). En otras palabras, la eficiencia potencial de un idioma cambia con un aumento en su "nivel" de forma no lineal y generalmente ambigua. Sin embargo, la velocidad de desarrollo y la complejidad de la modificación, la estabilidad y otros indicadores de calidad en sistemas complejos resultan ser mucho más importantes que la máxima velocidad de ejecución posible: distinguen entre un programa que funciona y uno que no [ 25]  - para que la evolución del hardware sea más factible económicamente (ejecutando más instrucciones por unidad de tiempo) y optimizando los métodos de compilación (además, durante las últimas décadas, la evolución del hardware se ha movido hacia el soporte de la optimización de métodos de compilación para lenguajes de alto nivel) . Por ejemplo, la recolección automática de basura , presente en la mayoría de los lenguajes de programación de alto nivel, se considera una de las mejoras más importantes que tienen un efecto beneficioso en la velocidad de desarrollo [26] .

Por lo tanto, en estos días, los lenguajes de bajo nivel se usan solo en problemas de programación de sistemas . La creencia generalizada es que en las tareas en las que es necesario un control preciso de los recursos, el lenguaje en sí debe requerir la menor cantidad de transformaciones posible, de lo contrario, todos los esfuerzos del programador serán en vano. De hecho, hay ejemplos que lo desmienten. Entonces, el lenguaje BitC es un representante de la cuarta generación ( paradigma de programación funcional ), pero está completamente enfocado en la programación de sistemas y compite con confianza en velocidad con C. Es decir, es un "lenguaje de alto nivel" destinado a la "programación de bajo nivel". Los lenguajes de tercera generación C# y Limbo se desarrollaron para su uso tanto en la programación del sistema (para aumentar la tolerancia a fallas del sistema operativo ) como en la programación aplicada, lo que garantiza la unidad de la plataforma, lo que reduce las pérdidas de traducción.

Idiomas seguros e inseguros

Las computadoras modernas representan datos complejos del mundo real como números en la memoria de la computadora. Esto introduce el riesgo de error humano en la disciplina de la programación , incluida la posibilidad de errores de acceso a la memoria . Por lo tanto, muchos lenguajes de programación van acompañados de un medio para controlar el significado de las operaciones en datos binarios en función de la información lógica que los acompaña: un sistema de tipos . Sin embargo, también existen lenguajes sin tipo , como Forth .

Los sistemas tipo de lenguajes se dividen en dinámicos (descendientes de Lisp , Smalltalk , APL ) y estáticos , y estos últimos, a su vez, se dividen en no polimórficos (descendientes de Algol y BCPL ) y polimórficos (descendientes de ML ) [ 27] . Además, se dividen en explícitos ( inglés  explícito ) e implícitos ( inglés  implícito ), en otras palabras, requieren una declaración explícita de tipos para objetos en el programa o los infieren estáticamente por sí mismos.

Hay sistemas de tipo fuerte y débil . Un sistema de tipo fuerte asigna un tipo a cualquier expresión de una vez por todas (siempre que suceda específicamente, de forma dinámica o estática ), mientras que un sistema de tipo débil le permite reasignar tipos más adelante. La tipificación fuerte a veces se identifica erróneamente con la tipificación estática.

En general, se dice que un lenguaje es seguro si los programas en él, que pueden ser aceptados por el compilador como bien formados, nunca van dinámicamente más allá de los límites de comportamiento aceptable [28] . Esto no significa que dichos programas no contengan ningún error. El término "buen comportamiento del programa" ( ing.  buen comportamiento ) significa que incluso si el programa contiene un cierto error (en particular, un error lógico ), no es capaz de violar la integridad de los datos y fallar ( ing . .  accidente ). Aunque los términos son informales, la seguridad de algunos lenguajes (por ejemplo , Standard ML ) es matemáticamente comprobable [27] . Otros (como Ada ) se han asegurado ad hoc sin proporcionar integridad conceptual, lo que puede ser desastroso si se confía en ellos para tareas críticas (ver integridad conceptual de los lenguajes ). La terminología informal fue popularizada por Robin Milner , uno de los autores de la teoría de la verificación formal y del propio lenguaje Standard ML .

El grado de control de errores y cómo reacciona el lenguaje ante ellos puede variar. Los sistemas de tipos más simples prohíben, por ejemplo, restar una cadena de un número entero . Sin embargo, tanto los milímetros como las pulgadas se pueden representar como números enteros , pero sería una falacia lógica restar pulgadas a los milímetros. Los sistemas de tipo desarrollados permiten (y los más desarrollados obligan) introducir dicha información lógica en el programa. Para una computadora, es redundante y se elimina por completo cuando se genera código de máquina de una forma u otra . En particular, Standard ML no permite ninguna operación sobre los datos, excepto aquellas que están explícitamente permitidas y formalizadas; sin embargo, los programas que contiene aún pueden terminar con una excepción no controlada (por ejemplo, al intentar dividir por cero ). Su descendiente, MLPolyR , también garantiza que no haya excepciones no controladas. Dichos lenguajes se denominan " type- safe ". Java y C# son menos estrictos y solo controlan las fugas de memoria , por lo que en su contexto a menudo usan el término más restringido " seguridad de tipo de memoria " o (más a menudo) simplemente " seguridad de acceso a la memoria " . Los lenguajes fuertemente tipados dinámicamente monitorean el comportamiento de los programas a lo largo del tiempo (lo que implica una degradación del rendimiento) y responden a los errores lanzando una excepción. Todos estos lenguajes están enfocados en la usabilidad , brindando el mejor compromiso entre evitar fallas graves y una alta velocidad de desarrollo del programa. Pero también hay lenguajes diseñados para escribir programas que son correctos por construcción , es decir, brindan una garantía de que el programa ejecutable será idéntico en estructura y comportamiento a su especificación (ver paramétrico , tipo dependiente ). Como consecuencia, los programas en dichos lenguajes a menudo se denominan "especificaciones ejecutables" (consulte la correspondencia de Curry-Howard ). La complejidad del desarrollo en tales lenguajes aumenta en órdenes de magnitud, además, requieren una calificación muy alta del desarrollador, por lo que se utilizan solo en la verificación formal . Ejemplos de tales lenguajes son Agda , Coq .  

Los lenguajes C y su descendiente C++ no son seguros [29] . En los programas en ellos, se encuentran ampliamente situaciones de debilitamiento del tipeo ( type casting ) y su violación directa ( juego de palabras ) , por lo que los errores de acceso a la memoria son una norma estadística en ellos (pero el bloqueo del programa no ocurre de inmediato, lo que lo hace difícil encontrar el lugar del error en el código). Los sistemas de análisis estático más potentes para ellos (como PVS-Studio [30] [31] ) son capaces de detectar no más del 70-80 % de los errores, pero su uso es muy caro en términos de dinero. Es imposible garantizar de manera confiable el funcionamiento sin fallas de los programas en estos lenguajes sin recurrir a la verificación formal , que no solo es más costosa, sino que también requiere conocimientos especiales. C también tiene descendientes seguros como Cyclone .

El lenguaje Forth no pretende ser "seguro", pero sin embargo, en la práctica, la existencia de programas que pueden corromper los datos es casi imposible, ya que un programa que contiene un error potencialmente peligroso se bloquea en la primera ejecución de prueba, forzando el código fuente. ser corregido. La comunidad de Erlang ha adoptado el enfoque "let it crash"   ,  también destinado a la detección temprana de errores .

Lenguajes compilados, interpretados e incrustados

Hay tres formas fundamentalmente diferentes de implementar lenguajes de programación: compilación , interpretación e inserción . Existe una idea errónea común de que la forma de implementación es una propiedad específica del lenguaje . De hecho, esta división es arbitraria hasta cierto punto. En varios casos, el lenguaje tiene una semántica formal orientada a la interpretación, pero todas o casi todas sus implementaciones reales son compiladores, a veces optimizadores muy eficientes (ejemplos son lenguajes de la familia ML , como Standard ML , Haskell ). Hay lenguajes que difuminan las líneas entre interpretación y compilación, como Forth .

La compilación significa que el código fuente del programa se convierte primero en el código de destino ( máquina ) mediante un programa especial llamado compilador  ; como resultado, se obtiene un módulo ejecutable , que ya se puede iniciar para su ejecución como un programa separado. La interpretación significa que el código fuente se ejecuta directamente, comando por comando (a veces con una preparación mínima, literalmente después de analizar el código fuente en AST ), de modo que el programa simplemente no se puede ejecutar sin un intérprete . La incrustación de idiomas se puede considerar filosóficamente como "implementación sin traducción " en el sentido de que dicho idioma es un subconjunto sintáctico y semántico de algún otro idioma, sin el cual no existe. Más precisamente, los lenguajes integrables agregan cuatro implementaciones más a las anteriores.

La forma natural de implementación del lenguaje está determinada por el momento de asociar los elementos del programa con sus características. En particular, en lenguajes con escritura estática , las variables y otros objetos del programa se asocian con el tipo de datos en la etapa de compilación, y en el caso de escritura dinámica  , en la etapa de ejecución, por regla general, en un punto arbitrario. en el programa. Algunas propiedades de los elementos del idioma, como el significado de los operadores aritméticos o las palabras clave de control, ya se pueden vincular en la etapa de definición del idioma. En otros idiomas es posible reasignarlos (ver enlace de nombres ). La vinculación temprana generalmente significa más eficiencia del programa, mientras que la vinculación posterior significa más flexibilidad, al precio de pasos más lentos y/o más complejos [32] . Sin embargo, incluso en casos aparentemente obvios, hay excepciones; por ejemplo, el polimorfismo intensional pospone el procesamiento de la tipificación estática hasta el tiempo de ejecución, pero no lo ralentiza, sino que aumenta el rendimiento general (al menos en teoría).

Para cualquier lenguaje compilado tradicionalmente (como Pascal ), se puede escribir un intérprete. Pero muchos lenguajes interpretados brindan algunas características adicionales, como la generación dinámica de código (ver eval ), por lo que su compilación debe ser dinámica (ver compilación dinámica ). Por lo tanto, el término compuesto "lenguaje + método de su implementación" en algunos casos es apropiado. Además, la mayoría de los intérpretes "puros" modernos no ejecutan construcciones de lenguaje directamente, sino que las compilan en alguna representación intermedia de alto nivel (por ejemplo, con desreferencia variable y expansión macro ). La mayoría de los lenguajes interpretados o compilados tradicionalmente se pueden implementar como incrustables , aunque no hay muchos metalenguajes que puedan cubrir otros lenguajes como su subconjunto ( Lisp es el representante más destacado ).

Como regla general, los programas compilados se ejecutan más rápido y no requieren programas adicionales para ejecutarse, ya que ya están traducidos a lenguaje de máquina. Al mismo tiempo, cada vez que se modifica el texto del programa, es necesario volver a compilarlo, lo que ralentiza el proceso de desarrollo. Además, un programa compilado solo puede ejecutarse en el mismo tipo de computadora, y generalmente bajo el mismo sistema operativo, para el cual fue diseñado el compilador. Para crear un ejecutable para un tipo diferente de máquina, se requiere una nueva compilación. Los lenguajes interpretados te permiten ejecutar programas inmediatamente después de un cambio, y en diferentes tipos de máquinas y sistemas operativos sin esfuerzo adicional, mientras que los homoicónicos te permiten  mover dinámicamente un programa entre diferentes máquinas sin interrumpir su funcionamiento (el caso más común de serialización ), lo que le permite desarrollar sistemas de disponibilidad continua ( ver también sistemas de alta disponibilidad ). La portabilidad de un programa interpretado está determinada únicamente por la disponibilidad de implementaciones de intérpretes para ciertas plataformas de hardware. A costa de todo esto, hay pérdidas de rendimiento notables; además, si el programa contiene un error fatal, este no se sabrá hasta que el intérprete alcance su lugar en el código (a diferencia de los lenguajes con seguridad estática ).

Algunos lenguajes, como Java y C# , se encuentran entre compilados e interpretados. Es decir, el programa no se compila en lenguaje de máquina, sino en código de bajo nivel independiente de la máquina, bytecode . A continuación, la máquina virtual ejecuta el código de bytes . Para ejecutar el código de bytes, generalmente se usa la interpretación, aunque algunas de sus partes se pueden traducir a código de máquina directamente durante la ejecución del programa usando la compilación Just-in-time ( JIT ) para acelerar el programa. Para Java, el código de bytes es ejecutado por la Máquina Virtual de Java ( JVM ), para C# - Common Language Runtime . Este enfoque, en cierto sentido, le permite utilizar las ventajas tanto de los intérpretes como de los compiladores.

Idiomas de primer y orden superior

Información inicial

La lógica matemática se clasifica por orden  : consulte la lógica de primer orden y la lógica de orden superior . Esta terminología es naturalmente heredada por la informática , formando semánticas, respectivamente, de primer orden y superior [33] . Los lenguajes de primer orden (por ejemplo, descendientes de Algol como Basic o el Pascal clásico de Wirth ) solo permiten definir dependencias de primer orden entre cantidades. Por ejemplo, el valor square xdepende del valor de x. Tales dependencias se llaman funciones . Los lenguajes de orden superior le permiten definir dependencias entre dependencias. Por ejemplo, el valor map f xdepende de los valores fy x, donde el fpropio valor expresa una dependencia abstracta (es decir, el parámetro f varía sobre un conjunto de funciones de una firma determinada ). Estas dependencias se denominan funciones de orden superior . Al mismo tiempo, en la mayoría de los casos, se dice que tal lenguaje considera las dependencias ( funciones ) como objetos de primera clase , en otras palabras, permite funciones de primera clase [34] (algunos lenguajes, como C , no no admite funciones de primera clase, pero brinda oportunidades limitadas para construir funciones de orden superior). Estos términos fueron introducidos por Christopher Strachey . Los lenguajes de orden superior incluyen casi todos los lenguajes funcionales (las excepciones son muy raras; un ejemplo de un lenguaje funcional de primer orden fue SISAL durante mucho tiempo , pero se le agregó soporte para funciones de primera clase en 2018). Con el desarrollo de los sistemas de tipos, la distinción de órdenes se extendió a los tipos (ver constructor de tipos ).

Expresividad

Los lenguajes de primer orden permiten incorporar algoritmos en el código , pero no la arquitectura del programa . Según Strachey , esta restricción fue heredada por el lenguaje Algol (y otros lenguajes de este) de las matemáticas clásicas, donde solo se utilizan operaciones y funciones constantes que son únicamente reconocibles fuera de contexto, y no existe una notación sistemática. para el trabajo arbitrario con funciones (como tal notación en la década de 1930, se construyó el cálculo lambda , que luego formó la base de lenguajes de orden superior) [35] . Los esquemas de interacción de componentes ( procedimientos , funciones , objetos , procesos , etc.) para programas en lenguajes de primer orden solo pueden existir en un nivel condicional, fuera de los propios programas. Con el tiempo, se descubrieron repetidamente esquemas similares de este tipo, como resultado de lo cual se construyó una metodología independiente a su alrededor: patrones de diseño . Los lenguajes de orden superior permiten que dichos esquemas se implementen como código ejecutable reutilizable (funciones diseñadas para transformar y componer otras funciones; consulte, por ejemplo, convertidores y escáneres en SML ) [36] [37] . Como resultado, soluciones que en lenguajes de primer orden pueden representarse mediante fragmentos de programa (a veces bastante complejos y engorrosos), en lenguajes de orden superior pueden reducirse a un solo comando o incluso al uso de un elemento del semántica propia del lenguaje que no tiene expresión sintáctica. Por ejemplo, el patrón " Comando " , a menudo usado en lenguajes de primer orden, es directamente equivalente a la noción misma de una función de primera clase . Lo mismo se aplica a las capas superiores de idiomas: mecanografía (ver polimorfismo en géneros superiores ) y mecanografía mecanografía (ver polimorfismo de géneros ).

Lo anterior aplica principalmente para lenguajes cuya semántica se basa en el cálculo lambda (descendientes de Lisp , ML ). Sin embargo, algunos lenguajes de diferente naturaleza también brindan programación de orden superior . Algunos ejemplos son los lenguajes de pila ( Forth ) y cierto tipo de lenguajes orientados a objetos ( Smalltalk , CLOS , consulte el mensaje de orden superior ).

Explorando

Al presentar la terminología de "entidades de primera y segunda clase", Strachey inmediatamente enfatizó que, por experiencia personal y conversaciones con muchas personas, estaba convencido de que era increíblemente difícil dejar de pensar en las funciones como objetos de segunda clase [ 35] . Es decir, el orden del lenguaje tiene una influencia psicológica pronunciada (ver la hipótesis de Sapir-Whorf ). El conocimiento de lenguajes de nivel superior ayudará al programador a pensar en términos de abstracciones de nivel superior [38] .

Los lenguajes de bajo nivel, por otro lado, pueden imponer lo contrario, en relación con lo cual es ampliamente conocida la siguiente afirmación:

Es casi imposible enseñar buena programación a estudiantes que han tenido experiencia en BASIC: como aspirantes a programadores, están mentalmente deformados sin esperanza de recuperación.

Texto original  (inglés)[ mostrarocultar] Es prácticamente imposible enseñar buena programación a estudiantes que han tenido una exposición previa a BASIC: como programadores potenciales, están mentalmente mutilados sin esperanza de regeneración. —Edsger Dijkstra

Esto significa que el uso de un lenguaje de orden superior no significa automáticamente un cambio en la arquitectura y un aumento en la reutilización ( no vea la bala de plata ): el factor determinante es la capacidad de un desarrollador particular para usar los lenguajes apropiados [39] .

Al comprender las posibilidades y limitaciones de las construcciones de alto nivel, los principios básicos de su implementación no solo le brindan al programador la oportunidad de usar el lenguaje que ha aprendido de manera más efectiva, sino que también le permite crear y usar mecanismos similares en caso de desarrollo en un lenguaje donde no están implementados [38] .

Será más fácil para un desarrollador que posee una gama más amplia de lenguajes de programación elegir entre ellos la herramienta más adecuada para resolver la tarea que tiene ante sí, aprender, si es necesario, un nuevo lenguaje o implementar un lenguaje específico de dominio . que, por ejemplo, incluyen una interfaz de línea de comandos bastante complicada [40] .

Paradigma de programación

La atribución de lenguas a paradigmas puede hacerse por varios motivos, algunos de los cuales corresponden a características técnicas específicas de las lenguas, mientras que otros son muy condicionales.

Técnicamente, los lenguajes se dividen, por ejemplo, en permitir efectos secundarios y referencialmente transparentes . En el segundo caso, se dice que el lenguaje pertenece a un " paradigma puramente funcional ". Ciertas propiedades del sistema de tipos y las estrategias de evaluación del lenguaje también se consideran a veces como un paradigma , por ejemplo, para sistemas de tipos paramétricamente polimórficos , a menudo se habla de la implementación del paradigma de programación genérico . Otro ejemplo es la propiedad de homoiconicidad , que abre todo un abanico de variedades de metaprogramación . Hay muchos "lenguajes heredados de las matemáticas" , muchos de los cuales forman paradigmas únicos. Representantes destacados son Lisp , que primero incorporó el cálculo lambda y por lo tanto sentó las bases para el paradigma funcional , Smalltalk , que primero incorporó el paradigma orientado a objetos (el Simula que apareció muchos años antes de que apoyara el concepto de una clase , pero incorporó el paradigma estructural ), y el lenguaje de pila de Forth , que incorpora el paradigma concatenativo .

Más convencionalmente, los idiomas se dividen en generaciones . Las dos primeras generaciones son de bajo nivel , es decir, centradas en las particularidades de un hardware concreto, y en principio no se corresponden con ningún paradigma (aunque un desarrollador concreto sobre ellas, claro, puede seguir ideológicamente determinadas tendencias). Junto con la tercera generación, forman un paradigma de programación imperativo , y las generaciones posteriores, uno declarativo ( para más detalles, consulte la sección Lenguajes de bajo y alto nivel ). Muchos lenguajes declarativos incluyen ciertas características imperativas, a veces al revés.

Con el crecimiento del tamaño y la complejidad de los programas, que ya utilizaban lenguajes de segunda generación, comenzó a formarse el paradigma de la programación procedimental , que requería la descomposición de grandes procedimientos en una cadena de procedimientos más pequeños relacionados jerárquicamente. Aproximadamente al mismo tiempo, aparecieron los primeros lenguajes de la tercera generación y se formó primero la programación estructurada como un desarrollo directo de procedimental , y luego modular . Con el tiempo, ha aparecido una gran cantidad de formas diferentes de resolver el problema de complicar los sistemas de software en crecimiento manteniendo el enfoque imperativo original en el núcleo. En algunos casos, se ha logrado un impacto significativo en la velocidad de desarrollo y los indicadores de calidad, pero en general, como se señaló anteriormente , los lenguajes de tercera generación se abstraen de la lógica de la máquina solo hasta cierto nivel y están ligeramente sujetos a transformaciones equivalentes . . Hasta la fecha, la tercera generación de lenguajes está representada por la gama más amplia de varios paradigmas.

La cuarta generación incluye lenguajes funcionales , de los cuales “puramente funcional” ( ing.  puramente funcional , correspondiente a la categoría técnica mencionada anteriormente de referencialmente transparente ), y el resto se denomina “no puramente funcional” ( ing.  impuramente funcional ).

La quinta generación incluye lenguajes de programación lógicos , en los que, además del tradicional, se distinguen varias formas especiales, por ejemplo, la programación con restricciones . De hecho, los lenguajes de quinta generación son lenguajes de cuarta generación complementados con una base de conocimiento [24]  ; por lo tanto, esta categoría, como se señaló anteriormente, generalmente no se acepta.

Muchos paradigmas son métodos declarados condicionalmente para organizar la estructura de un programa y son aplicables a una gran variedad de lenguajes. Estructural y modular tienen la cobertura más amplia  : se utilizan tanto en lenguajes imperativos como declarativos . Otros paradigmas están estrechamente relacionados con las propiedades técnicas. Por ejemplo, un subconjunto del lenguaje C ++ - plantillas  - puede considerarse formalmente como un lenguaje puramente funcional completo de Turing , pero C ++ no tiene las propiedades inherentes a los lenguajes funcionales ( transparencia referencial , seguridad de tipo , llamada de cola garantía de optimización , etc.). Como consecuencia, los algoritmos utilizados en la compilación de lenguajes funcionales no se pueden aplicar a C++ y, por lo tanto, los principales investigadores del paradigma funcional son muy escépticos sobre C++ (para más detalles, consulte la crítica de las plantillas de C++ ).

Lenguajes para programar a pequeña y gran escala

Los programas pueden resolver problemas de varias escalas : un programa crea un cronograma para una función determinada y el otro administra el flujo de trabajo de una gran empresa. Los diferentes lenguajes de programación están diseñados para diferentes escalas de tareas iniciales y, lo que es más importante, manejan el crecimiento de la complejidad de los sistemas de software de diferentes maneras. La cualidad clave del lenguaje, que determina cómo cambia la complejidad del desarrollo a medida que crece el sistema, es la abstracción , es decir, la capacidad de separar el significado (comportamiento) de un componente del sistema de la forma en que se implementa [41] [42 ] .

El crecimiento de la complejidad de cualquier sistema de software está fundamentalmente limitado por el límite hasta el cual todavía es posible mantener el control sobre él: si la cantidad de información requerida para comprender un componente de este sistema excede la "capacidad" del cerebro de uno persona, entonces este componente no será completamente entendido. Será extremadamente difícil refinarlo o corregir errores, y se puede esperar que cada corrección introduzca nuevos errores debido a este conocimiento incompleto.

Texto original  (inglés)[ mostrarocultar] Existe un límite fundamental en la complejidad de cualquier sistema de software para que siga siendo manejable: si requiere más de "un cerebro completo" de información para comprender un componente del sistema, entonces ese componente no se comprenderá por completo. Será extremadamente difícil realizar mejoras o corregir errores, y es probable que cada corrección introduzca más errores debido a este conocimiento incompleto. — Martin Ward, "Programación orientada al lenguaje" [43]

Los indicadores de calidad del código fuente , como la capacidad de prueba y la modificabilidad, están obviamente determinados por el factor de reutilización . Esto puede significar aplicar diferentes funciones al mismo componente o poder aplicar la misma función a diferentes componentes. Los sistemas de tipos paramétricamente polimórficos (especialmente inferenciales ) y dinámicos aumentan en gran medida el factor de reutilización : por ejemplo, una función que calcula la longitud de una matriz será aplicable a un número infinito de tipos de matrices [27] [44] . Si el lenguaje requiere que en la firma de la función se indique una forma específica de implementar los datos de entrada, entonces este coeficiente sufre mucho. Por ejemplo, Pascal ha sido criticado por tener que especificar siempre un tamaño de matriz específico [45] y C++ ha sido criticado por tener que  distinguir cuando se refiere a componentes de datos compuestos [46] . Los lenguajes de orden superior \u003e le permiten resaltar los esquemas de interacción de funciones en un bloque de código llamado repetidamente ( función de orden superior ) [36] [47] , y la reutilización alcanza sus valores más altos cuando pasar a un lenguaje de un nivel superior, si es necesario, especialmente desarrollado para una tarea determinada  , en este caso, el lenguaje se reutiliza en lugar de una sola función [43] , y el desarrollo del lenguaje en sí puede llevarse a cabo con reutilización intensiva de componentes del compilador [48] . .->

Con el desarrollo de los lenguajes, aparecieron categorías especiales (inherentes solo a la programación, no requeridas previamente en matemáticas) de componentes y dependencias: mónadas , clases de tipos , ramas polimórficas , aspectos , etc. Su uso le permite expresar más funcionalidad en la misma cantidad de código, traduciendo así la programación - grande a una escala más pequeña.

Otros problemas fundamentales asociados a la complejidad de los grandes sistemas radican fuera de los propios programas: se trata de la interacción de los programadores que lo desarrollan entre sí, la documentación , etc. Además de aportar abstracción , la integridad conceptual del lenguaje de programación elegido juega un papel significativo en esto [49] [43] .

Además de las propiedades de la semántica del lenguaje, la reutilización se puede proporcionar a través de la estructura modular de un sistema o complejo de software. Además, por muy flexible que sea el lenguaje, trabajar con grandes cantidades de código, especialmente muchas personas, requiere que se descompongan en módulos de una forma u otra. La estructura modular implica no solo dividir el código fuente de un programa monolítico en muchos archivos de texto, sino proporcionar una abstracción a mayor escala, es decir, definir una interfaz para cualquier fragmento lógicamente completo y ocultar los detalles de su implementación. En función de las reglas de ámbito aplicadas en el idioma, el idioma puede o no permitir el ámbito de dependencia automático. Si, de acuerdo con las reglas , es posible un conflicto de nombres, entonces la detección automática de dependencias es imposible, y luego en el encabezado del módulo se requiere enumerar explícitamente los nombres de los módulos cuyos componentes se utilizan en él.

Algunos lenguajes (como Basic o el clásico Pascal de Wirth ) están enfocados únicamente al desarrollo de programas pequeños y estructuralmente simples. No proporcionan ni un sistema desarrollado de módulos, ni la flexibilidad de fragmentos específicos. El lenguaje C se creó como un "ensamblador de alto nivel", lo que en sí mismo no implica el desarrollo de sistemas por encima de un cierto umbral de complejidad, por lo que tampoco se incluyó el soporte para la programación a gran escala. Algunos lenguajes de alto y ultra alto nivel ( Erlang , Smalltalk , Prolog ) proporcionan como elementos primitivos básicos conceptos que en otros lenguajes son estructural y algorítmicamente complejos ( procesos , clases , bases de conocimiento) -similares a varios lenguajes matemáticos-. cálculos ( ver también integridad conceptual de los lenguajes ). Por lo tanto, estos lenguajes a menudo se consideran específicos del dominio  : algunas tareas (pero no todas) parecen simples en ellos, que parecen complejas en otros idiomas. Sin embargo, extender la funcionalidad de otras formas en estos lenguajes puede ser difícil. Standard ML y sus parientes se estratifican en dos idiomas, de los cuales uno - " lenguaje central " ( ing.  core language ) - se centra en el desarrollo de programas simples, y el otro - " modular language " ( ing.  module language ), - respectivamente, sobre su disposición no lineal en sistemas de software complejos. Con el tiempo, se crearon opciones para fusionarlos ( 1ML ). Muchos otros lenguajes también incluyen sistemas de módulos , pero la mayoría son lenguajes de módulos de primer orden . El lenguaje de módulo ML es el único lenguaje de módulo de orden superior de su tipo . Los lenguajes Lisp y Forth le permiten hacer crecer los sistemas de manera arbitraria e ilimitada, incluso permitiéndole crear lenguajes específicos de dominio incrustables dentro de sí mismos (como su subconjunto sintáctico y semántico); por lo tanto, a menudo se les llama metalenguajes .

El enfoque más popular para resolver el problema de la complejación en la actualidad es la programación orientada a objetos , aunque el éxito de su aplicación durante las décadas de su existencia ha sido repetidamente objeto de escepticismo, y todavía no hay evidencia confiable de que brinde beneficios en comparación con otros enfoques. en términos de otros indicadores de calidad . Se acompaña (y en ocasiones compite) de diversas tecnologías de regulación de dependencias entre componentes: metaclases , contratos , prototipos , mixins , traits , etc.

Históricamente, se ha considerado un enfoque más poderoso el uso de varias formas de metaprogramación , es decir, la automatización del propio proceso de desarrollo en varios niveles. Hay una diferencia fundamental entre la metaprogramación externa al lenguaje y la disponible en el propio lenguaje. Cuando se utilizan lenguajes de primer orden la complejidad de los sistemas de software en crecimiento cruza rápidamente el umbral de las habilidades de una persona en la percepción y el procesamiento de la información, por lo tanto, los medios externos de diseño visual preliminar se utilizan para estudiar esquemas complejos de forma simplificada . formulario y luego generar automáticamente un marco de código .CASE ._- En las comunidades de desarrolladores que utilizan lenguajes de orden superior , domina el enfoque opuesto: para evitar la posibilidad misma de que la complejidad se salga de control al dividir los modelos de información en componentes independientes y desarrollar herramientas para convertir automáticamente un modelo en otro: ver lenguaje -programación orientada .

Integridad conceptual de los lenguajes

Frederick Brooks [50] y C. E. R. Hoare [51] enfatizan la necesidad de asegurar la integridad conceptual de los sistemas de información en general y de los lenguajes de programación en particular, para que cada parte del sistema utilice formas sintácticas y semánticas similares y no necesite ser dominado además del propio sistema de composición así como las reglas para su uso idiomático . Hoare predijo que la complejidad de Ada provocaría catástrofes. Alan Kay distingue los lenguajes que son “ cristalización de estilo   de otros lenguajes que son “ aglutinación de rasgos ” [ 52] . Greg Nelson [53] y Andrew Appel [ 27] colocan los lenguajes derivados matemáticamente en una categoría especial .  

Estos énfasis requieren el uso de lenguajes que incorporen algún tipo de cálculo matemático, cuidadosamente adaptado para ser un lenguaje más práctico para desarrollar programas reales. Dichos lenguajes son ortogonales , y si bien esto significa que muchos de los modismos comunes que están disponibles como lenguajes primitivos en los lenguajes más populares deben implementarse manualmente, la expresividad de dichos lenguajes generalmente puede ser sustancialmente mayor.

Solo unos pocos idiomas entran en esta categoría; la mayoría de los idiomas están diseñados teniendo en cuenta la prioridad para una traducción eficiente a una máquina de Turing . Muchos lenguajes se basan en teorías generales, pero durante el desarrollo casi nunca se prueba la seguridad de compartir elementos específicos del lenguaje que son aplicaciones particulares de estas teorías, lo que inevitablemente conduce a la incompatibilidad entre las implementaciones del lenguaje. Estos problemas se ignoran o se presentan como un fenómeno natural ( ej.  "no es un error, sino una característica" ), pero en realidad son causados ​​por el hecho de que el lenguaje no ha sido sometido a análisis matemático [54] .

Ejemplos de lenguajes de base matemática y los modelos matemáticos que implementan:

Tener una base matemática para un idioma puede garantizar (o al menos prometer con una probabilidad muy alta) algunas o todas las siguientes propiedades positivas:

  • Incremento significativo en la estabilidad del programa. En algunos casos, construyendo una prueba de confiabilidad para el propio lenguaje (ver tipo de seguridad ), simplificando significativamente la verificación formal de los programas, e incluso obteniendo un lenguaje que es en sí mismo un sistema de prueba automático ( Coq , Agda ). En otros casos, debido a la detección temprana de errores en las primeras ejecuciones de prueba de los programas ( Forth y expresiones regulares ).
  • Garantizar una eficacia del programa potencialmente mayor. Incluso si la semántica del lenguaje está lejos de la arquitectura de la plataforma de compilación de destino, se le pueden aplicar métodos formales de análisis de programas globales (aunque la complejidad de escribir incluso un traductor trivial puede ser mayor). Por ejemplo, para los lenguajes Scheme y Standard ML , se han desarrollado compiladores y supercompiladores que optimizan el programa completo , cuyo resultado puede competir con confianza en velocidad con el lenguaje C de bajo nivel e incluso superar a este último (aunque el consumo de recursos del los propios compiladores resulta ser mucho más alto). Uno de los DBMS más rápidos  , KDB [57] ,  está escrito en lenguaje K. El lenguaje Scala (que heredó las matemáticas de ML ) proporciona una velocidad más rápida en la plataforma JVM que su lenguaje Java nativo . . Por otro lado, Forth tiene fama de ser uno de los lenguajes que más recursos consume (menos exigente que C ) y se utiliza para desarrollar aplicaciones en tiempo real para las computadoras más pequeñas; además, el compilador Forth es uno de los que requiere menos tiempo para implementar en ensamblador .
  • Un límite previamente conocido (ilimitado o, por el contrario, claramente definido) al crecimiento de la complejidad de los componentes, sistemas y complejos de software que pueden expresarse utilizando este lenguaje manteniendo indicadores de calidad [27] [58] . Los lenguajes que no tienen una justificación matemática (es decir, estos son los más utilizados en la corriente principal : C++ , Java , C# , Delphi , etc.), en la práctica, limitan la funcionalidad implementada y/o reducen la calidad como el sistema se vuelve más complejo [59] , ya que son inherentes curvas de crecimiento exponencial de la complejidad, tanto en relación con el trabajo de una persona individual, como en relación con la complejidad de gestionar el proyecto como un todo [49] [60] . La complejidad prevista del sistema conduce a una descomposición por fases del proyecto en muchas tareas más pequeñas, cada una de las cuales se resuelve con el lenguaje correspondiente, o a la programación orientada al lenguaje para el caso en que la tarea dirigida por el lenguaje es precisamente la descripción . de semántica y/o cálculos simbólicos ( Lisp , ML , Haskell , Refal , Expresiones regulares ). Los lenguajes con un límite de crecimiento ilimitado para la complejidad de los programas a menudo se denominan metalenguajes (lo que no es cierto en la interpretación directa del término, pero es reducible en la práctica, ya que cualquier mini-lenguaje elegido para resolver un determinado una subtarea como parte de una tarea general puede representarse como un subconjunto sintáctico y semántico de un lenguaje dado sin necesidad de traducción [61] ).
  • Conveniencia para una persona en la resolución de problemas para los que este lenguaje está orientado por naturaleza (ver lenguaje específico de dominio ), lo que, en cierta medida, también puede (indirectamente) afectar el aumento de la estabilidad de los programas resultantes al aumentar la probabilidad de detección errores en el código fuente y reduciendo la duplicación de código.

Categorías especiales de idiomas

Transformaciones formales y optimización

VF Turchin señala [62] que las ventajas de cualquier idioma formalizado están determinadas no solo por lo conveniente que es para el uso directo de una persona, sino también por la medida en que los textos en este idioma son susceptibles de transformaciones formales.

Por ejemplo, la transparencia referencial significa que no es necesario evaluar los parámetros de la función antes de llamarlos; en cambio, la expresión real pasada puede sustituirse por completo por la variable en la función, y el comportamiento de la función no cambiará a partir de esto. Esto abre la posibilidad de transformaciones automáticas casi arbitrarias de programas : se pueden eliminar representaciones de datos intermedios innecesarias, se pueden reducir cadenas complejas de cálculos, se puede seleccionar el número óptimo de procesos paralelos , introducir memorización , etc. Por otro lado, esto significa una ausencia total de efectos secundarios , y esto hace que la implementación de algunos algoritmos sea notoriamente menos eficiente que cuando se usa el estado mutable .

Para programas pequeños y simples, los lenguajes de alto nivel producen un código de máquina más grande y se ejecutan más lentamente. Sin embargo, para programas algorítmica y estructuralmente complejos, la ventaja puede estar del lado de algunos lenguajes de alto nivel, ya que una persona no es físicamente capaz de expresar conceptos complejos, teniendo en cuenta su ejecución efectiva en un lenguaje de máquina. Por ejemplo, hay un punto de referencia en el que MLton y Stalin Scheme están con confianza por delante de GCC . Hay muchas razones particulares por las que la optimización automática durante la traducción de lenguajes de alto nivel otorga , en principio , una mayor velocidad de ejecución que el control consciente sobre el método de implementación en lenguajes de bajo nivel. Por ejemplo, existe buena evidencia de que la gestión automática de la memoria es más eficiente que la gestión manual de la memoria solo cuando se utiliza un método dinámico (consulte la recolección de elementos no utilizados ) [63] , pero también existe un método estático potencialmente más eficiente (consulte la administración de la memoria basada en regiones). ). Además, para cada microcontexto, es necesario asignar registros , teniendo en cuenta la minimización del acceso a la memoria, y esto requiere resolver el problema de coloración de gráficos . Hay muchas características de este tipo en la lógica de la máquina, por lo que la complejidad general de la información aumenta exponencialmente con cada “paso hacia abajo de un nivel”, y la compilación de un lenguaje de alto nivel puede incluir docenas de esos pasos.

Hay muchas estrategias para la optimización automática . Algunos son universales, otros pueden aplicarse solo a idiomas de cierta naturaleza y algunos dependen de la forma en que se usa el idioma. Un ejemplo es la optimización de llamada de cola y su caso especial: optimización de recursión de cola . Aunque los compiladores en muchos lenguajes implementan optimización de recursión de cola bajo ciertas condiciones, solo unos pocos lenguajes pueden garantizar semánticamente la optimización de llamada de cola en el caso general. El estándar de lenguaje Scheme requiere que cada implementación lo garantice. Para muchos lenguajes funcionales , en principio es aplicable, pero solo los compiladores de optimización lo implementan. En lenguajes como C o C++ , solo se puede hacer en ciertos casos, y solo cuando se usa el análisis de flujo de control global [64] .

Los lenguajes de orden superior tienden a funcionar más lento que los lenguajes de primer orden en la mayoría de los casos. Las razones radican tanto en la descomposición del código lineal en una cadena de llamadas anidadas, como en las características resultantes de la representación de bajo nivel de funciones (ver cierre ) y datos (envueltos ( casilla en inglés  ), etiquetados). Sin embargo, existen técnicas agresivas de optimización de programas que permiten reducir los lenguajes de orden superior a lenguajes de primer orden (ver defuncionalización, MLton , Stalin Scheme [ ).

Popularidad de los idiomas

Es difícil determinar qué lenguaje de programación es el más popular, ya que el significado de la palabra "popularidad" depende del contexto (en inglés se usa el término "usage", que tiene un significado aún más vago). Un lenguaje puede tomar la mayor cantidad de horas de trabajo , otro tiene la mayor cantidad de líneas de código, un tercero requiere la mayor cantidad de tiempo de CPU y un cuarto es la base de mayor investigación en la academia. Algunos idiomas son muy populares para tareas específicas. Por ejemplo, Cobol todavía domina los centros de datos empresariales , Fortran domina  las aplicaciones científicas y de ingeniería, las variaciones del lenguaje C  dominan la programación de sistemas y varios descendientes de ML  dominan la verificación formal . . Regularmente se utilizan otros lenguajes para crear una amplia variedad de aplicaciones.

Hay varias métricas para medir la popularidad de los idiomas, cada una de las cuales está diseñada con un sesgo hacia un sentido particular del concepto de popularidad:

  • contando el número de vacantes que mencionen el idioma;
  • el número de libros vendidos (libros de texto o libros de referencia);
  • una estimación de la cantidad de líneas de código escritas en el idioma (que no tiene en cuenta los casos de uso de idiomas que rara vez se publican);
  • contar las menciones de idiomas en las consultas de los motores de búsqueda .

Cabe señalar que las puntuaciones altas de estos indicadores no solo no indican en modo alguno un alto nivel técnico del lenguaje y/o una optimización de costes a la hora de utilizarlo, sino que, por el contrario, en ocasiones pueden indicar lo contrario. Por ejemplo, el lenguaje Cobol es uno de los líderes en cuanto a la cantidad de líneas de código escritas en él, pero la razón de esto es la tasa extremadamente baja de código modificable, lo que hace que este código no sea reutilizable , sino código heredado . Como resultado, el mantenimiento de los programas Cobol es mucho más costoso a corto plazo que los programas en la mayoría de los lenguajes modernos, pero reescribirlos desde cero requeriría una importante inversión única y solo puede compararse con el costo a largo plazo. La imperfección técnica de Cobol se debe al hecho de que fue desarrollado sin la participación de expertos en el campo de la informática [65] [66] .

Véase también

Notas

  1. ISO/IEC/IEEE 24765:2010 Ingeniería de software y sistemas - Vocabulario
  2. ISO/IEC 2382-1:1993, Tecnología de la información - Vocabulario - Parte 1: Términos fundamentales
  3. Lista de lenguajes de programación  (inglés)  (enlace inaccesible) . Consultado el 3 de mayo de 2004. Archivado desde el original el 12 de junio de 2004.
  4. Rojas, Raúl , et al. (2000). "Plankalkül: el primer lenguaje de programación de alto nivel y su implementación". Institut für Informatik, Freie Universität Berlin, Informe técnico B-3/2000. (texto completo) Archivado el 18 de octubre de 2014 en Wayback Machine .
  5. Computer Languages, 1989 , 1. Constructor invisible § Creación de códigos legibles por humanos, p. dieciséis.
  6. Linda Null, Julia Lobur, Lo esencial de la organización y la arquitectura informática , Edición 2, Jones & Bartlett Publishers, 2006, ISBN 0-7637-3769-0 , p. 435
  7. O'Reilly Media. Historia de los lenguajes de programación (PDF)  (enlace no disponible) . Consultado el 5 de octubre de 2006. Archivado desde el original el 28 de febrero de 2008.
  8. Frank da Cruz. Tarjetas perforadas de IBM Historia de la computación de la Universidad de Columbia .
  9. Richard L. Wexelblat: Historia de los lenguajes de programación , Academic Press, 1981, capítulo XIV.
  10. Pratt, 1979 , 4.6. Coincidencia de patrones, pág. 130-132.
  11. Pratt, 1979 , 15. Snobol 4, pág. 483-516.
  12. Pratt, Zelkowitz, 2002 , 8.4.2. Coincidencia de patrones, pág. 369-372.
  13. François Labelle. Gráfico de uso del lenguaje de programación (enlace no disponible) . forjafuente . Consultado el 21 de junio de 2006. Archivado desde el original el 17 de junio de 2006. 
  14. Hayes, Brian.  Las guerras del punto y coma  // Científico estadounidense :revista. - 2006. - vol. 94 , núm. 4 . - pág. 299-303 .
  15. Tetsuro Fujise, Takashi Chikayama, Kazuaki Rokusawa, Akihiko Nakase (diciembre de 1994). "KLIC: una implementación portátil de KL1" Proc. de FGCS '94, ICOT Tokio, diciembre de 1994. http://www.icot.or.jp/ARCHIVE/HomePage-E.html Archivado el 25 de septiembre de 2006 en Wayback Machine KLIC es una implementación portátil de un lenguaje de programación de lógica concurrente KL1 .
  16. JimBender. Mini-Bibliografía sobre Módulos para Lenguajes de Programación Funcional (enlace no disponible) . ReadScheme.org (15 de marzo de 2004). Consultado el 27 de septiembre de 2006. Archivado desde el original el 24 de septiembre de 2006. 
  17. Stroustrup, Bjarne Evolución de un lenguaje en y para el mundo real: C++ 1991-2006 .
  18. T. Pratt, M. Zelkowitz. Lenguajes de programación. Desarrollo e implementación. - 4. - San Petersburgo: Peter, 2002. - S. 203. - 688 p. - 4000 copias.  - ISBN 5-318-00189-0 .
  19. Stroustrup B. El diseño y la evolución de C++ . - San Petersburgo: Peter, 2006. - S. 74-76. — 448 págs. - 2000 copias.  — ISBN 5-469-01217-4 .
  20. Seibel - Coders at Work, 2011 , Capítulo 12. Ken Thompson, p. 414.
  21. Zuev E.A., Krotov A.N., Sukhomlin V.A. El lenguaje de programación C++: etapas de evolución y estado actual (4 de octubre de 1996). Fecha de acceso: 16 de enero de 2017.
  22. Paulson, "ML para el programador que trabaja", 1996 , p. 213.
  23. Paulson, "ML para el programador que trabaja", 1996 , p. una.
  24. 1 2 3 Mernik, 2012 , pág. 2-12.
  25. Paulson, "ML para el programador que trabaja", 1996 , p. 9.
  26. Rick Byers. Algoritmos de recolección de basura . cursos.cs.washington.edu. - Proyecto para CSEP 521, invierno de 2007. Consultado el 28 de diciembre de 2016.
  27. 1 2 3 4 5 Appel: una crítica del estándar ML, 1992 .
  28. Harper - Fundamentos prácticos para lenguajes de programación, 2012 , Capítulo 4. Estática, p. 35.
  29. Mitchell, 2004 , 6.2.1 Tipo de seguridad, p. 132-133.
  30. Comparación de analizadores de código estático: CppCat, Cppcheck, PVS-Studio y Visual Studio
  31. Comparación de PVS-Studio con otros analizadores de código
  32. Pratt, 1979 , 2.7. Encuadernación y tiempo de encuadernación, s. 46-51.
  33. Reynolds, "Teorías de los lenguajes de programación", 1998 , 12.4 Derivación de una semántica de primer orden.
  34. Strachey - Conceptos fundamentales, 1967 , 3.5.1. Objetos de primera y segunda clase., p. 32-34.
  35. 1 2 Strachey - Conceptos fundamentales, 1967 , 3.5.1. Objetos de primera y segunda clase, p. 32-34.
  36. 12 SICP._ _ _
  37. Harper - Fundamentos prácticos para lenguajes de programación, 2012 , 8.2 Funciones de orden superior, p. 67.
  38. 1 2 Pratt, Zelkowitz, 2002 , 1.1 Por qué aprender lenguajes de programación, p. 17-18.
  39. Bruce A. Tate. Prólogo // Siete idiomas en siete semanas: una guía pragmática para aprender lenguajes de programación . - Estantería Pragmática, 2010. - P.  14-16 . — ISBN 978-1934356593 .
  40. Pratt, Zelkowitz, 2002 , 1.1 Por qué aprender lenguajes de programación, p. Dieciocho.
  41. Aho, Ulmán, 1992 .
  42. Joyner, 1996 , 2.2 Comunicación, abstracción y precisión, p. cuatro
  43. 1 2 3 Ward, 1994 .
  44. Paulson, "ML para el programador que trabaja", 1996 , p. 63-64.
  45. Kernigan sobre Pascal, 1981 .
  46. Joyner, 1996 , 3.17'.' y '->', pág. 26
  47. Paulson, "ML para el programador que trabaja", 1996 , p. 177-178.
  48. Hudak, 1998 .
  49. 1 2 Brooks, 1975, 1995 .
  50. Brooks, 1975, 1995 , Lograr la integridad conceptual, p. treinta.
  51. CAR Hoare - El traje viejo del emperador, Comunicaciones de la ACM, 1981
  52. Alan Kay . La historia temprana de Smalltalk . — Apple Computer, ACM SIGPLAN Notices, vol.28, n.º 3, marzo de 1993.
  53. Greg Nelson. Programación de Sistemas con Modula-3. - Nueva Jersey: Prentice Hall, Englewood Cliffs, 1991. - 288 p. — ISBN 978-0135904640 .
  54. Comentario sobre SML, 1991, Objetivos del comentario , p. vii.
  55. Thomas Noll, Chanchal Kumar Roy. Modelado de Erlang en Pi–Calculus . - ACM 1-59593-066-3/05/0009, 2005.
  56. Principios de diseño detrás de Smalltalk
  57. kx: rendimiento calibrado
  58. Luca Cardelli. Programación tipográfica . — Informes de vanguardia de IFIP, Springer-Verlag, 1991.
  59. Ward, 1994 : "Hay un límite fundamental a la complejidad de cualquier sistema de software para que aún sea manejable: si requiere más de "un cerebro lleno" de información para entender un componente del sistema, entonces ese componente no será entendido completamente. Será extremadamente difícil realizar mejoras o corregir errores, y es probable que cada corrección introduzca más errores debido a este conocimiento incompleto".
  60. Vidrio, 2004 .
  61. Czarnecki y otros, 2004 .
  62. Turchin V. F. Transformaciones equivalentes de programas en REFAL: Actas de TsNIPIASS 6: TsNIPIASS, 1974.
  63. B.Zorn. El costo medido de la recolección conservadora de basura. Informe Técnico CU-CS-573-92. // Universidad de Colorado en Boulder. - 1993. - doi : 10.1.1.14.1816 .
  64. Ehud Lamm .
  65. Richard L.Conner. Cobol, tu edad se nota  // Computerworld  :  revista. — Grupo Internacional de Datos, 1984. - 14 de mayo ( vol. 18 , núm. 20 ). — P.ID/7—ID/18 . — ISSN 0010-4841 .
  66. Robert L. Mitchell. Cobol: aún no muerto . Computerworld (4 de octubre de 2006). Consultado: 27 de abril de 2014.

Literatura

  • Gavrikov M. M., Ivanchenko A. N., Grinchenkov D. V. Fundamentos teóricos para el desarrollo e implementación de lenguajes de programación. - KnoRus , 2013. - 178 p. - ISBN 978-5-406-02430-0 .
  • Krinitsky N. A., Mironov G. A., Frolov G. D. Programación. - GIFML, 1963. - 384 p.
  • Bratchikov I. L. Sintaxis de los lenguajes de programación. - Ciencia , 1975. - 230 p.
  • Lavrov S. S. Conceptos básicos y construcciones de lenguajes de programación. - Finanzas y estadísticas, 1982. - 80 p.
  • Terence Pratt. Lenguajes de programación: desarrollo e implementación = Diseño e Implementación de Lenguajes de Programación (PLDI). — 1ª edición. - Mundo , 1979.
  • Alfred Aho, Ravi Seti, Jeffrey Ullman. Compiladores: principios, tecnologías y herramientas. - Addison-Wesley Publishing Company, Williams Publishing House, 1985, 2001, 2003. - 768 p. - ISBN 5-8459-0189-8 (ruso), 0-201-10088-6 (original).
  • Libros de la vida del tiempo. Lenguaje informático = Lenguajes informáticos. - M. : Mir, 1989. - T. 2. - 240 p. - (Comprensión de Computadores). — 100.000 copias.  — ISBN 5-03-001148-X .
  • Luca Cardelli . Programación tipificada( (inglés)) // Informes de Estado del Arte IFIP. - Springer-Verlag, 1991. -vol. Descripción formal de los conceptos de programación. -S. 431-507.
  • Alfred Aho, Jeffrey Ullman. Fundamentos de la Informática. — Prensa informática, 1992.
  • Lawrence C. Paulson . ML para el programador de trabajo. — 2do. - Cambridge, Gran Bretaña: Cambridge University Press, 1996. - 492 p. -ISBN 0-521-57050-6(tapa dura), 0-521-56543-X (tapa blanda).
  • John C. Reynolds. Teorías de los lenguajes de programación . - Cambridge University Press, 1998. - ISBN 978-0-521-59414-1 (tapa dura), 978-0-521-10697-9 (tapa blanda).
  • Andrew W. Appel. Implementación del compilador moderno en ML (en C, en Java)  (neopr.) . - Cambridge, Gran Bretaña: Cambridge University Press, 1998. - 538 p. - ISBN (ML) 0-521-58274-1 (tapa dura), 0-521-60764-7 (tapa blanda).
  • Robert W. Sebesta. Conceptos básicos de lenguajes de programación \u003d Conceptos de lenguajes de programación / Per. De inglés. - 5ª ed. - M. : Williams , 2001. - 672 p. - 5000 copias.  — ISBN 5-8459-0192-8 (ruso), ISBN 0-201-75295-6 (inglés).
  • Wolfenhagen V. E. Diseños de lenguajes de programación. Métodos de descripción. - M. : Centro YurInfoR, 2001. - 276 p. — ISBN 5-89158-079-9 .
  • Parondzhanov V. D. Cómo mejorar el trabajo de la mente. Algoritmos sin programadores: ¡es muy simple! - M. : Delo, 2001. - 360 p. — ISBN 5-7749-0211-0 .
  • Pierce, Benjamin C. Tipos y lenguajes de programación . - MIT Press , 2002. - ISBN 0-262-16209-1 .
    • Traducción al ruso: Pierce B. Tipos en lenguajes de programación. - Dobrosvet , 2012. - 680 p. — ISBN 978-5-7913-0082-9 .
  • Terence Pratt, Marvin Zelkowitz. Lenguajes de programación: desarrollo e implementación. - 4ª edición. - Peter, 2002. - (Clásicos de la Informática). - ISBN 978-5-318-00189-5 .
  • Juan C Mitchell Conceptos en lenguajes de programación. - Cambridge University Press, 2004. - ISBN 0-511-04091-1 (eBook en netLibrary); 0-521-78098-5 (tapa dura).
  • Pedro Seibel . Codificadores en el trabajo. Reflexiones sobre la profesión de programador. - Symbol-Plus, San Petersburgo. - 2011. - ISBN 978-5-93286-188-2 , 978-1-4302-1948-4 (inglés).

Enlaces