Els llenguatges de programació: tipologia i evolució

Anuncio
Los lenguajes de programación: tipología y evolución
Els llenguatges de programació: tipologia i evolució
Índice de contenido
Els llenguatges de programació: tipologia i evolució...................................................................................1
Licencia......................................................................................................................................................1
Concepto y evolución histórica..................................................................................................................1
Conceptos básicos..................................................................................................................................1
Especificación de un lenguaje................................................................................................................1
Compilación, interpretación y depuración..............................................................................................3
Tipologías...................................................................................................................................................4
Imperativos.............................................................................................................................................4
Estructurados.....................................................................................................................................4
Orientados a objetos..........................................................................................................................4
Orientados a aspectos........................................................................................................................5
Declarativos............................................................................................................................................5
Funcionales.......................................................................................................................................6
Lógicos..............................................................................................................................................6
Evolución histórica....................................................................................................................................7
Primera generación: código máquina.....................................................................................................7
Segunda generación: Ensamblador.........................................................................................................7
Tercera generación: Lenguajes de alto nivel..........................................................................................7
Cuarta generación: Lenguajes declarativos no procedurales..................................................................8
Futuro: Programación por especificaciones, programación visual, lenguaje natural..............................8
Licencia
Este obra de Jesús Jiménez Herranz está bajo una licencia Creative Commons AtribuciónLicenciarIgual 3.0 España.
Basada en una obra en oposcaib.wikispaces.com.
Concepto y evolución histórica
Conceptos básicos
Un lenguaje de programación es un conjunto de comandos y construcciones sintácticas que
permiten representar un algoritmo en un ordenador. La idea es que sirvan como interfaz entre el
lenguaje humano (fácil pero poco riguroso) y el lenguaje máquina (preciso pero difícil).
Se dice que un lenguaje es universal o Turing-completo si tiene un poder computacional
equivalente a una máquina de Turing, es decir, si cualquier algoritmo implementable por una
máquina de Turing puede ser expresado en términos de ese lenguaje. La gran mayoría de lenguajes
de programación son Turing-completos, existiendo excepciones como las fórmulas matemáticas de
una hoja de cálculo, o las expresiones regulares.
Gran parte de los lenguajes de programación se basan en los mismos conceptos básicos definidos
por la arquitectura von Neumann y la máquina de Turing: la transformación de datos en una serie de
pasos secuenciales. Ahora bien, existen muchos otros que se alejan de este paradigma, existiendo
así diferentes tipologías de lenguajes.
Especificación de un lenguaje
Un lenguaje se define formalmente mediante gramáticas libres de contexto. Una gramática libre
de contexto (GLC) es una tupla G(T,N,R,I), donde:
Jesús Jiménez Herranz, http://oposcaib.wikispaces.com
1
Los lenguajes de programación: tipología y evolución
●
●
●
●
T es un conjunto de elementos terminales
N es un conjunto de elementos no terminales
R es un conjunto de reglas de transformación, en el que cada regla r es una función
r : T  P T ∪N 
I es el símbolo no terminal inicial de la gramática, I ∈ N
Una gramática G define un lenguaje, compuesto por todas aquellas combinaciones de elementos
terminales que pueden ser producidos mediante la aplicación de un conjunto de reglas al símbolo
inicial de la gramática.
Generalmente una gramática se especifica mediante sus reglas, quedando implícitos los
terminales y no terminales en las propias reglas, y el elemento inicial I como el que aparece en la
primera regla.
Por ejemplo, una gramática que defina el lenguaje de las expresiones aritméticas sobre números
enteros podría ser la siguiente:
Expr: Expr Oper Expr | (Expr) | Num
Oper: + | - | * | /
Num: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
En este caso, los símbolos terminales serían los dígitos del 0 al 9, los operadores matemáticos y
los paréntesis, mientras que los símbolos no terminales serían Expr, Oper y Num. Para hacer más
compacta la notación, se unen las reglas con el mismo símbolo origen, separando el resultado por
barras verticales.
Para saber si una expresión pertenece al lenguaje de esta gramática, hay que encontrar una
secuencia de reglas que lleve a ella desde el símbolo inicial. Por ejemplo, para la cadena “5*(2+3)”
podríamos encontrar la secuencia:
Expr -► Expr Oper Expr -► Expr * Expr -► Num * Expr -► 5*Expr -►
5*(Expr) -► 5*(Expr Oper Expr) -► 5*(Expr+Expr) -► 5*(Expr+Num) -►
5*(Num+Num) -► 5*(2+Num) -► 5*(2+3)
En el caso de los lenguajes de programación, se acostumbra a utilizar la notación BNF, propuesta
por Backus y Naur (Backus-Naur Form). Por ejemplo, la siguiente gramática BNF especificaría una
versión simplificada del Pascal:
<PROGRAMA>
<MAIN>
<FUNCIONES>
::=
::=
::=
<FUNCION>
<PARAMETROS>
::=
::=
<LINEAS>
::=
<LINEA>
::=
<IDENTIFICADOR>::=
<EXPRESION>
::=
<OPERACION>
::=
program <FUNCIONES> <MAIN>
begin <LINEAS> end.
<FUNCION> <FUNCIONES> |
<FUNCION>
[procedure|function] (<PARAMETROS>) begin <LINEAS> end;
<IDENTIFICADOR>,<PARAMETROS> |
<IDENTIFICADOR>
<LINEA>; <LINEAS> |
<LINEA>
<IDENTIFICADOR> := <EXPRESION>
[a-zA-Z0-9] <IDENTIFICADOR> |
[a-zA-Z0-9]
<IDENTIFICADOR> |
<EXPRESION> <OPERACION> <EXPRESION> |
<IDENTIFICADOR>(<PARAMETROS>)
+ | - | * | /
Jesús Jiménez Herranz, http://oposcaib.wikispaces.com
2
Los lenguajes de programación: tipología y evolución
Esta gramática reconocería programas como este:
program
function promedio(a,b)
begin
temp:=a+b;
promedio:=temp/2;
end;
begin
n1:=5;
n2:=13;
resultado:=promedio(n1,n2);
end.
Compilación, interpretación y depuración
El hecho de utilizar un lenguaje de programación hace necesario un procesado posterior que
traduzca este lenguaje al código máquina inteligible por el ordenador. Hay básicamente dos formas
de llevar a cabo este proceso:
●
●
Compilación: El programa se transforma en su equivalente en código máquina, y se almacena
así para el momento de la ejecución.
Interpretación: El programa se almacena como código fuente, y en el momento de la ejecución
se va traduciendo a código máquina conforme es necesario.
Cada uno de estos esquemas tiene sus ventajas e inconvenientes. Las ventajas de la compilación
serían:
●
Código más rápido y eficiente: Como sólo se hace una vez, se puede invertir un tiempo en
optimizar a fondo el programa resultante.
Por el contrario, los lenguajes interpretados tienen otras ventajas respecto a los compilados:
●
Mayor portabilidad: Al almacenarse los programas en código de alto nivel, para llevar el
programa de una arquitectura a otra basta con que exista un intérprete.
Acceso al código: Es posible acceder y modificar el código de un programa interpretado en
cualquier momento.
Posibilidad de optimizar para la máquina destino: En el caso de familias de ordenadores, es
posible optimizar de forma más eficiente al conocer los detalles del ordenador en que se está
ejecutando. En la compilación, se optimiza para el mínimo común denominador, lo que no
siempre aprovecha las características avanzadas de las máquinas más modernas.
●
●
Si bien los lenguajes interpretados tienen muchas ventajas, su bajo rendimiento ha lastrado su
uso en determinados entornos en los que es deseable una buena velocidad de ejecución. De todas
formas, existen medidas que alivian estas diferencias. Por ejemplo, en vez de ir interpretando el
código a medida que se ejecuta, los intérpretes JIT (Just In Time) hacen una compilación la primera
vez que se arranca el programa, guardándola para ejecuciones sucesivas. Esto, añadido a la
posibilidad de hacer optimizaciones más específicas para la máquina concreta que en el caso de la
compilación previa, hace que las diferencias de rendimiento entre lenguajes compilados e
interpretados se hayan reducido, e incluso que en algunas ocasiones resulte más eficiente utilizar un
programa interpretado.
Esta convergencia en el rendimiento, sumada a las características de portabilidad y versatilidad
de los lenguajes interpretados han hecho que éstos se extiendan en gran manera en ámbitos como el
de los servidores o el desarrollo de aplicaciones web. En realidad, tanto los lenguajes más utilizados
Jesús Jiménez Herranz, http://oposcaib.wikispaces.com
3
Los lenguajes de programación: tipología y evolución
actualmente en estos entornos (Java), como los nuevos desarrollos en lenguajes (Python, Ruby) son
interpretados.
Tipologías
Imperativos
Los lenguajes imperativos son los más cercanos al modelo de la máquina de Turing, en el sentido
de que expresan un programa como un conjunto secuencial de acciones que actúan sobre los datos y
el estado de la máquina.
Concretamente, un lenguaje imperativo dispone de los siguientes elementos:
●
●
●
Variables en las que guardar valores temporales
Operaciones para operar sobre las variables
Instrucciones de salto para alterar el flujo de ejecución
Todos los lenguajes de ensamblador son imperativos por motivos obvios, así como muchos
lenguajes de alto nivel. La programación imperativa se subdivide en otros paradigmas, como la
programación estructurada o la programación orientada a objetos.
Estructurados
El paradigma estructurado es un refinamiento del imperativo. Así, un programa estructurado se
compone de los siguientes elementos:
●
●
●
Bloques de sentencias que se ejecutan secuencialmente (procedimientos o funciones).
Estructuras de selección del bloque a ejecutar a partir de una condición (condicionales).
Estructuras de repetición de bloques de código según una condición (bucles).
En concreto, el paradigma estructurado elimina el concepto de salto, que queda implícito en los
bucles, las construcciones condicionales y la división del código en procedimientos.
El paradigma estructurado tiene una serie de ventajas:
●
●
Mayor legibilidad del código, lo que facilita la programación y el mantenimiento.
Encapsulamiento de la funcionalidad, lo que facilita las modificaciones futuras y el testeo.
Ejemplos de lenguajes estructurados serían el C o el Pascal.
Orientados a objetos
La orientación a objetos supone un cambio importante respecto al paradigma estructurado. La
idea subyacente es que la separación entre los datos y las operaciones que los manipulan que
establece el modelo estructurado es artificial, ya que en el mundo real son aspectos que van de la
mano. Por otra parte, en el modelo estructurado no hay diferencias claras entre las diferentes
entidades que son relevantes a un programa, cuando en realidad los problemas a resolver consisten
generalmente en interacciones entre diferentes entidades.
Así, el paradigma de la orientación a objetos propone plantear los problemas en términos de unas
ciertas entidades, llamadas objetos, y sus interacciones. Cada uno de estos objetos estará compuesto
de unos datos, así como de ciertas operaciones sobre los mismos. La orientación a objetos se basa
en tres puntos fundamentales:
Jesús Jiménez Herranz, http://oposcaib.wikispaces.com
4
Los lenguajes de programación: tipología y evolución
●
●
●
Encapsulación: Los programas están formados por objetos, con sus datos y operaciones sobre
ellos. Además, los detalles de cada objeto generalmente no son relevantes para otros objetos,
por lo que sólo son accesibles por el objeto propietario.
Herencia: Los objetos se constituyen como una jerarquía de clases, de manera que una clase
puede heredar características de otra, ampliando o modificando su funcionalidad. Esto
concuerda con numerosas problemáticas del mundo real, en la que hay evidentes patrones de
herencia.
Polimorfismo: Capacidad de que objetos de diferentes tipos respondan a métodos que, aunque
tienen el mismo nombre, actúan de forma diferente en cada tipo. Esto permite abstraer las
diferencias de implementación de las operaciones para cada tipo, y escribir programas a muy
alto nivel. Por ejemplo, el operador “+” puede usarse tanto para sumar enteros como para
concatenar cadenas.
Java o C++ serían ejemplos de lenguajes orientados a objetos.
Orientados a aspectos
Este paradigma de programación es una evolución de la programación orientada a objetos. La
idea subyacente es que gran parte del código del programa, pese a tener una temática común, no se
puede encapsular convenientemente y afecta de manera horizontal a toda la aplicación.
Por ejemplo, en una aplicación de gestión probablemente haya que gestionar la conexión a las
bases de datos, la autenticación, o el logging de sucesos. Por tanto, probablemente en cada método
de la lógica de negocio sea necesaria algún tipo de intervención referente a estos aspectos,
volviéndose el código menos legible y dificultando su mantenimiento en el caso de que alguno de
estos aspectos cambie en el futuro. Aun encapsulando estas gestiones en clases independientes, sería
necesario añadir en cada método del programa las llamadas correspondientes, por lo que no se
resuelve el problema.
Lo que propone la POA es encapsular estas funcionalidades comunes en entidades llamadas
aspectos. Cada aspecto, además de la implementación de su funcionalidad, indicaría su relación con
el resto de elementos del programa. Así, en el ejemplo anterior, se crearían los aspectos Seguridad,
BD y Logging, estableciendo que los diferentes métodos de la lógica de negocio hacen referencia a
ellos. Por tanto, el proceso con POA es:
●
●
●
Identificar clases y aspectos
Implementar por separado
Enlazar aspectos a las clases que los necesiten (weaving)
Ventajas de la POA:
●
●
El código es más legible
Es posible añadir funcionalidad sin tocar el código principal, sólo añadiendo un aspecto
Para implementar POA se puede usar desde preprocesadores hasta lenguajes específicos, pasando
por extensiones de lenguajes existentes.
Declarativos
Los lenguajes declarativos, en oposición a los imperativos, no indican una serie de pasos a
seguir, sino que indican el resultado que se desea obtener. La implementación concreta necesaria
para llegar a ese resultado es algo interno al compilador.
Jesús Jiménez Herranz, http://oposcaib.wikispaces.com
5
Los lenguajes de programación: tipología y evolución
Al no indicar los detalles de implementación del algoritmo, sino únicamente el resultado, los
lenguajes declarativos dejan mucho más margen al compilador para la optimización, por lo que se
utilizan comúnmente para programas de alta concurrencia, difíciles de programar directamente.
Un ejemplos típico de lenguaje declarativo serían las consultas de SQL, en las que se indica el
resultado a obtener, dejando al SGBD los detalles sobre cómo recorrer las tablas, qué índices
utilizar, etc.
Funcionales
La programación funcional concibe la ejecución de un programa como la evaluación de
diferentes funciones matemáticas. En este caso, no existe el concepto de una serie secuencial de
sentencias, ni de una máquina central con estado, por lo que este paradigma es completamente
independiente del modelo de la máquina de Turing.
Algunas características de estos lenguajes son:
●
●
●
●
●
Todo son funciones, que devuelven datos y/o otras funciones
Muy paralelizables si las funciones son puras (reentrantes)
La gestión de memoria está implícita
Recursividad en vez de iteratividad (que es un caso particular de recursividad)
Fácil probar matemáticamente corrección
Tradicionalmente, los lenguajes funcionales han sido más lentos que los imperativos, si bien
implementan aspectos como la gestión de memoria y construcciones recursivas complejas que son
ignorados por los lenguajes imperativos. Conforme los lenguajes imperativos han ido
implementando características como la recolección automática de basura, o técnicas avanzadas de
orientación a objetos (como la reflexión), la diferencia se ha reducido, y a día de hoy no es
relevante. De hecho, en ámbitos científicos en los que se requiere una gran capacidad de cálculo, es
común ver lenguajes típicamente imperativos como Fortran sustituidos por otros funcionales, que
permiten expresar los cálculos de una forma más limpia y compacta y ofrecen unas capacidades de
paralelización que los hacen especialmente apropiados para la tarea.
La que posiblemente sea la principal dificultad de los lenguajes funcionales es el aprendizaje de
un nuevo paradigma completamente diferente al tradicional de la máquina de Turing. Debido a esto,
los lenguajes funcionales tienen un ámbito de aplicación reducido, básicamente, a entornos
académicos y aplicaciones científicas y de inteligencia artificial.
Algunos lenguajes funcionales son Lisp y sus dialectos (si bien dispone de muchas extensiones
que hacen que no sea puramente funcional), o Haskell. Muchos lenguajes imperativos modernos
como Python o Ruby están también claramente influenciados por los lenguajes funcionales, y
adoptan algunas de sus construcciones, por lo que se pueden usar de forma más o menos funcional.
El carácter abstracto y altamente paralelo de los lenguajes funcionales puros los hacen
especialmente apropiados para la computación cuántica, y de hecho ya existen dialectos de Haskell
y otros lenguajes funcionales en este sentido.
Lógicos
En un lenguaje lógico, un programa se compone de una base de conocimiento en forma de
predicados de lógica de primer orden. El funcionamiento del programa consistiría en hacer
preguntas a esta base de conocimiento, cuya respuesta sería deducida de estos predicados. Por
ejemplo, si consideramos la siguiente base de conocimiento:
Jesús Jiménez Herranz, http://oposcaib.wikispaces.com
6
Los lenguajes de programación: tipología y evolución
ave  x⇒ vuela x
ave  paloma
aveaguila 
¬ave perro
podríamos preguntar cosas como:
vuela  paloma?
verdadero
vuela?
paloma , aguila
Las aplicaciones principales de los lenguajes lógicos están en el ámbito de la inteligencia
artificial y los sistemas expertos, si bien son directamente aplicables a determinados problemas
comunes (p. ej. cálculo de horarios) que son difíciles de resolver por métodos imperativos.
Por otra parte, los programas lógicos requieren un trabajo importante de construcción de la base
de conocimientos, son difíciles (si no imposibles) de depurar, y además el tiempo de ejecución
puede dispararse con facilidad.
El principal lenguaje lógico es el Prolog.
Evolución histórica
Primera generación: código máquina
La primera generación de lenguajes de programación consistía en escribir programas en código
máquina, es decir, introduciendo directamente en el ordenador los valores binarios correspondientes
a los códigos de instrucción, datos, etc., generalmente mediante interruptores y palancas.
Cronológicamente, este esquema fue el que se uso en los inicios de la informática, en los años 40 y
50.
Segunda generación: Ensamblador
La segunda generación de lenguajes se corresponde con la aparición del lenguaje ensamblador.
El ensamblador no era más que una representación textual del código máquina que se usaba hasta
entonces, de manera que a cada instrucción se le asignase un nombre identificativo, y los datos
pudieran ser introducidos en decimal/hexadecimal en vez de directamente en binario. Un programa
ensamblador se encargaba de traducir el texto del código ensamblador a formato binario inteligible
por el ordenador.
Tercera generación: Lenguajes de alto nivel
La tercera generación incluye a la mayoría de lenguajes modernos, y se refiere a la aparición de
lenguajes de alto nivel. Estos lenguajes ofrecen estructuras sintácticas complejas, que permiten
implementar fácilmente funcionalidades como bucles o condicionales, difíciles de implementar
directamente con el juego de instrucciones de un ordenador. A partir del programa, un programa
compilador se encarga de traducir el código de alto nivel a su equivalente en ensamblador y/o
código máquina.
Uno de los primeros lenguajes de alto nivel fue Fortran, creado a finales de los años 50.
Dentro de esta generación se encuentran tanto los primeros lenguajes imperativos como Fortran o
Cobol, como lenguajes lógicos y funcionales como Prolog o Lisp. También se incluyen aquí
Jesús Jiménez Herranz, http://oposcaib.wikispaces.com
7
Los lenguajes de programación: tipología y evolución
lenguajes estructurados como Pascal o C, o lenguajes orientados a objetos como Smalltalk, C++ o
Java.
Cuarta generación: Lenguajes declarativos no procedurales
Si bien a partir de la tercera generación la terminología es más difusa, generalmente se entiende
como lenguajes de cuarta generación a los lenguajes declarativos, que a diferencia de los
imperativos especifican el objetivo a calcular (objetivo explícito, procedimiento implícito) en lugar
de los pasos concretos a seguir (objetivo implícito, procedimiento explícito). En esta categoría se
encontrarían lenguajes como SQL, Mathematica o Postscript.
Generalmente, los lenguajes de 4GL, aunque en muchos casos son Turing-completos, están
orientados a un dominio específico, como es el caso de las bases de datos en SQL o la impresión de
documentos en Postscript.
Futuro: Programación por especificaciones, programación visual, lenguaje natural
Hay quien habla de una quinta generación de lenguajes de programación, en la que, por ejemplo,
se programaría mediante especificaciones de cómo debe ser el programa, sin indicar los detalles de
implementación. Lenguajes como Prolog o SQL apuntan hacia esta dirección.
Otra propuesta de lenguaje de quinta generación sería un lenguaje completamente visual, en el
que los diferentes componentes serían bloques que podrían ensamblarse unos con otros de forma
totalmente gráfica, y sin entrar en detalles de implementación. En este sentido se ha comparado el
desarrollo de software con otros ámbitos de la ingeniería, como la construcción o el diseño de
maquinaria, en los que en lugar de hacer cada diseño desde cero, se utilizan una serie de
componentes comunes para llevar a cabo diseños más complejos.
Finalmente, el objetivo final de la programación sería poder especificar al ordenador cómo debe
ser un programa directamente usando el lenguaje natural. Esto entra en el ámbito de la inteligencia
artificial, por lo que ni siquiera está claro que sea un problema resoluble, ni por otra parte que sea la
mejor forma de especificar un programa dadas la poca precisión y las ambigüedades intrínsecas a
este tipo de lenguaje.
Jesús Jiménez Herranz, http://oposcaib.wikispaces.com
8
Descargar