Introducción a la Programación

1 Algoritmos, Programas y Lenguajes

1.1 Introducción

Un autómata es cualquier mecanismo capaz de realizar un trabajo de forma autónoma (un reloj, una caja de música, la cisterna del WC, un radiador con termostato).

Todos estos aparatos tienen en común que, una vez conectados, pueden realizar su función sin más intervención externa. También comparten el hecho de que son bastante simples. Unos autómatas más flexibles serían un organillo, un video, una lavadora, ya que al menos su repertorio de acciones posibles es más variado. El ejemplo del organillo es revelador ya que en él aparecen las mismas fases que en el desarollo de un programa: una pieza de música es "diseñada" por un composistor, codificada en un soporte físico y ejecutada por una máquina.

En estor términos, un ordenador es un autómata de cálculo gobernado por un programa, de tal modo que diferentes programas harán trabajar al ordenador de distinta forma. Un programa es la codificación de un algoritmo, y un algoritmo es la descripción precisa de una sucesión de instrucciones que permiten llevar a cabo un trabajo en un número finito de pasos.

Así, un ordenador es probablemente el más flexible de los autómatas, ya que la tarea a ejecutar puede ser descrita por cualquier algoritmo que el usuario esté dispuesto a codificar.

Los ordenadores nos permiten hacer tareas más eficiente y rápida y con más precisión de lo que seríamos capaces de conseguir a mano, en el caso de que pudieramos hacerlas a mano.

Para utilizar esta herramienta, debemos especificar exactamente lo que queremos hacer y el orden en el que debe hacerse. Esto se hace mediante la programación, y por ello nos interesa aprender a programar.

Para escribir una secuencia de instrucciones que ha de seguir una computadora, debemos seguir cierto procedimiento compuesto de una fase de resolución del problema y de una fase de implementación:

fases de resolución e
implementación

La documentación y el mantenimiento también son partes de la programación, y las veremos a lo largo del discurso de la materia.

1.2 Algoritmo

El origen de la palabra se piensa que es el nombre de un algebrista árabe llamado Mûsâ al-Khowârizmî, AD 825.

Un algoritmo es un procedimiento especificado paso a paso para resolver un problema en una cantidad finita de tiempo.

Por ejemplo está el algoritmo de cambiar una rueda pinchada en un automovil:

  1. Quitar la rueda.
    1. Aflojar los tornillos.
    2. Levantar el automovil.
    3. Extraer la rueda.
      1. Retirar los tornillos.
      2. Retirar la rueda.
  2. Poner la rueda.
    1. Colocar la rueda.
      1. Presentar la rueda.
      2. Poner los tornillos.
    2. Bajar el coche.
    3. Apretar los tornillos.

En el algoritmo anterior muchos de los pasos son demasiado vagos para ser considerados bien definidos; de modo que habría que concretarlos más.

Los algoritmos se suelen expresar utilizando una manera mas o menos formal de lenguaje, llamado pseudocódigo, que toma diversas formas, alguna de ellas más gráficas, y otras más cercanas al lenguaje humano.

Los primeros algoritmos conocidos están fechados entre los años 3000 y 1500 AC. Fueron encontrados en la zona conocida como Mesopotamia (actualmente Irak) cerca de la ciudad de Babilonia. Estos algoritmos no tenían ni condicionales (los expresaban escibiendo varias veces el algoritmo) ni iteraciones (extendían el algoritmo con tantos pasos como fuera necesario). Problablemente más conocido, es el matemático griego Euclides, que hacia el año 300 AC., definió un algoritmo para calcular el máximo común divisor de dos enteros positivos. Aunque en este algoritmo se incluía la iteración, presentaba pocas mejoras respecto a los anteriores.

El algoritmo de Euclides trabaja con dos enteros m y n com m>n>0 y se describe como sigue:

mientras m > 0 hacer

devolver n

Más recientes fueron las aportaciones en esta campo de Charles Babbage, quien entre los años 1820 y 1850, diseñó dos máquinas para computar ninguna de las cuales fue terminada. La más interesante, ya que se parecía a los computadores digitales modernos, fue la máquina analítica. Los programas para este primer computador estaban escritos, básicamente, en lenguaje máquina y utilizaban unas ciertas tarjetas de operación y tarjetas de datos. Junto a Babbage trabajó Ada Augusta, condesa de Lovelace, hija del famoso poeta Lord Byron, que recientemente ha sido reconocida como la primera programadora y en su honor se llamó ADA al último lenguaje desarrollado bajo el auspicio del Dpto. de Defensa de los USA.

Durante los años 30 y 40 surge un gran número de gente con ideas sobre notaciones para la programación. La mayoría eran puramente teóricas. Algunos ejemplos son la máquina de Turing o el cálculo Lambda.

A principios de los años 50 se empezaron a construir máquinas con cierta capacidad de cálculo (aunque muy limitadas y costosas), que trabajaban con lenguajes de tipo ensamblador.

1.3 Evolución de los lenguajes

Los métodos de diseño e implementación de los lenguajes de programación han evolucionado rápidamente desde los primeros lenguajes de alto nivel que aparecieron a principios de los años 30.

1.3.1 Perspectiva histórica

Comenzaremos por seguir el desarrollo histórico de los lenguajes de programación actuales.

Las primeras generaciones

Antes de que un computador pueda ejecutar una tarea, debe programársele para que lo haga colocando en la memoria principal un algoritmo apropiado, expresado en lenguaje máquina, que no es más que una secuencia de números mediante los que se representan las operaciones a realizar y los operandos con los que operar. Originariamente, este proceso de programación se realizaba por el laborioso método de expresar todos los algoritmos en el lenguaje de máquina, enfoque que hacía más penosa la ya de por sí difícil tarea de diseñar un programa, y en la mayoría de los casos daba pie a errores que era necesario localizar y corregir.

El primer paso para eliminar estas complejidades del proceso de programación fue la asignación de nombres mnemónicos a los diversos códigos de operación, y usarlos en vez de la representación hexadecimal, aumentando considerablemente la comprensibilidad de las secuencias de instrucciones de la máquina. Luego un programa especial llamado ensamblador se encargaba la traducción de los códigos mnémonicos a instrucciones en lenguaje máquina. A estos programas se les llamó ensambladores, pues su tarea era ensamblar instrucciones en lenguaje máquina a partir de códigos de operación y operandos. Por extensión, a los lenguajes que utilizaban los mnemónicos se les llamó lenguajes ensamblador.

En la época en que aparecieron los primeros lenguajes ensambladores, parecía que se había dado un gigantesco salto hacia adelante en la búsqueda de mejores entornos de programación, y es por ello que se les comenzó a llamar lenguajes de segunda generación, siendo la primera generación la compuesta por los lenguajes máquina. Estamos en los primeros años 50 y ejemplos de estos lenguajes podrían ser AUTOCODER, SPS, BAL o EASYCODER.

Una consecuencia importante de la íntima asociación entre los lenguajes ensambalador y de máquina es que cualquier programa escrito en lenguaje ensamblador depende inherentemente de la máquina; esto es, las instrucciones del programa se expresan en términos de los atributos de una máquina específica. Por tanto, un programa escrito en lenguaje ensamblador no se puede transportar fácilmente a otra máquina porque se tiene que reescribir de modo que se ajuste a la configuración de registros y al conjunto de instrucciones de la nueva máquina.

Otra desventaja de los lenguajes ensambladores es que el programador, aunque no tiene que codificar las intrucciones en forma de patrones de bits, sí está obligado a pensar en térnimos de los pequeños pasos incrementales del lenguaje de la máquina, y no puede concentrarse en la solución global de la tarea que está realizando. En pocas palabras, las instrucciones elementales en que se debe expresar finalmente un programa no son necesariamente las instrucciones que deben usarse al diseñarlo.

La tercera generación

De acuerdo con esta idea, a mediados de los 50 se comenzaron a crear lenguajes de programación que eran más propicios para la elaboración de software que los lenguajes ensamblador de bajo nivel. El resultado fue la aparición de una tercera generación de lenguajes de programación que difería de las anteriores en que sus instrucciones eran de alto nivel y además independientes de las máquinas. Una vez escrito el programa con instrucciones de alto nivel, se utilizaba un programa llamado traductor que traducía a lenguaje máquina los programas escritos en lenguajes de alto nivel. Estos programas son parecidos a los ensambladores de la segunda generación, solo que a menudo tienen que compliar, reunir varias instrucciones de máquina para formar secuencias cortas que simularan la actividad solicitada por la instrucción de alto nivel, y por esta razón se comenzó a llamar compiladores a este tipo de programa.

Unos de los primeros lenguajes de la tercera generación son FORTRAN y COBOL, por lo que algunas clasificaciones les colocan en la segunda generación. También está ALGOL60. Después de estos aparecen otros como BASIC (1965), SNOBOL, APL, PL/1 y SIMULA, entre otros. En los 70 aparecen lenguajes de alto nivel como PASCAL, C, MODULA y PROLOG. Más recientes son Eiffel, Smalltalk, ADA, ML, C++ y Java.

Con la aparición de los lenguajes de tercera generación se alcanzó, en gran medida la meta de la independiencia respecto a las máquinas. Como las instrucciones de estos lenguajes no hacían referencia a los atributos de ninguna máquina en particular, se podían compilar con la misma facilidad en una u otras máquinas. Así, en teoría, un programa escrito en un lenguaje de tercera generación se podía utilizar en cualquier máquina con sólo aplicar el compilador apropiado. Sin embargo, la realidad no ha resultado ser tan simple.

Las tres primeras generaciones de lenguajes siguen una sucesión en el tiempo, mientras que a partir de ahí, las dos últimas generaciones han evolucionado paralelamente y en campos bastantes distintos.

La cuarta generación

Los lenguajes de cuarta generación pretenden superar los problemas surgidos de los lenguajes de tercera generación:

Estos lenguajes permiten generar aplicaciones de cierta complejidad con un número de líneas menor que el que tendríamos si usáramos un lenguaje de tercera generación. Para construir aplicaciones el programador contará, aparte de un conjunto de instrucciones secuenciales, con una gran diversidad de mecanismos como son: el rellenado de formularios, la interacción con la pantalla, etc. Un ejemplo de este tipo de lenguajes es SQL.

La quinta generación

La quinta generación de lenguajes se ha relacionado con los lenguajes que se utilizan en el campo de la inteligencia artificial: sistemas basados en el conocimiento, sistemas expertos, mecanismos de inferencia o procesamiento del lenguaje natual. Lenguajes como LISP o PROLOG han sido la base de este tipo de lenguajes.

1.4 Lenguajes Orientado al Objeto

Vamos a centrar nuestro estudio en un lenguaje orientado a objetos, y antes de ver las características de este tipo de lenguajes es bueno dar un repaso de los diferentes tipos de lenguajes de programación que han existido.

1.4.1 Clasificación de los lenguajes

Aunque existan cientos de lenguajes diferentes, estos se pueden agrupar segun las distintas filosofías que han seguido y la forma de trabajar que implican.

Lenguajes procedimentales

El paradigma por procedimientos, también conocido como paradigma imperativo, representa el enfoque tradicional del proceso de programación. Se define el proceso de programación como el desarrollo de procedimientos que, al seguirse, manipulan los datos para producir el resultado deseado. Así, el paradigma por procedimientos nos dice que abordemos un problema tratando de hallar un método para resolverlo.

Lenguajes declarativos

En contraste, consideremos el paradigma declarativo que hace hincapié en la pregunta ¿Cuál es el problema? en vez de ¿Qué procedimiento necesitamos para resolver el problema?. Lo importante aquí es descubrir e implantar un algoritmo general para la resolución de problemas, después de lo cual se podrán resolver éstos con sólo expresarlos en una forma compatible con dicho algoritmo y aplicarlo. En este contexto, la tarea del programador se reduce a crear un enunciado preciso del problema, más que a descubrir un algoritmo para resolverlo.

Desde luego, el principal obstáculo para crear un lenguaje de programación basado en el paradigma declarativo es el descubrimiento del algorimo básico para resolver problemas. Por esta razón, los lenguajes declarativos tienden a ser de propósito específico, diseñados para usarse en aplicaciones particulares.

Lenguajes funcionales

El paradigma funcional contempla el proceso de creación de programas como la construcción de cajas negras, cada una de las cuales acepta entradas y produce salidas. Los matemáticos llaman funciones a tales cajas, y es por ello que este enfoque se denomina paradigma funcional. Las primitivas de un lenguaje de programación funcional consisten en funciones elementales a partir de las cuales el programador debe construir las funciones más elaboradas necesarias para resolver el problema en cuestión.

1.4.2 Lenguajes Orientados al Objeto

Otro enfoque para la creación de programas es el paradigma orientado a objetos, OO. El término programación orientada a objetos se refiere a un estilo de programación por lo que un lenguaje orientado a objetos puede ser tanto imperativo como declarativo; lo que los caracteriza es la forma de manejar la información: clase, objeto y herencia. En este enfoque, las unidades de datos se consideran objetos activos, en vez de las unidades pasivas contempladas por el paradigma por procedimientos tradicional. Para aclarar esto, consideremos una lista de nombres. En el paradigma por procedimientos, esta lista no es más que una colección de datos, y cualquier programa que quiera trabajar con ella deberá incluir los algoritmos para realizar las manipulaciones requeridas. Así, la lista es pasiva en el sentido de que la mantiene un programa controlador, en vez de tener la responsabilidad de mantenerse ella misma. En cambio, en el enfoque orientado a objetos la lista se considera como un objeto formado por la lista misma y por una serie de rutinas para manipularla. Por ejemplo, pueden ser rutinas para insertar una nueva entrada en la lista, para detectar si la lista está vacía o para ordenar la lista. De este modo, un programa que obtiene acceso a esta lista no necesita contener algoritmos para efectuar esas tareas; simplemente aprovecha las rutinas suministradas en el objeto. En cierto sentido, en vez de ordenar la lista como en el paradigma por procedimientos, el programa pide a la lista que se ordene a sí misma.

Muchas ventajas del paradigma orientado a objetos son consecuencias de la estructura modular que surge como subproducto natural de la filosofía orientada a objetos, pues cada objeto se implanta como un módulo individual, bien definido, cuyas características son en gran medida independientes del resto del sistema. Así, una vez desarrollado un objeto que representa a una determinada entidad, es posible reutilizar ese objeto cada vez que se requiera dicha entidad. De hecho, un rasgo primoridal de los lenguajes de programación orientados a objetos es la capacidad de representar definiciones de objetos a modo de esqueletos, clases, que pueden usarse una y otra vez para construir múltiples objetos con las mismas propiedades, herencia, o modificarse para construir nuevos objetos con propiedades similares.

La programación OO está adquiriendo popularidad a grandes pasos, y muchos creen que dominará el campo de la programación en el futuro.

El lenguaje orientado a objetos más conocido es Smalltalk, que apareció en 1976. Es un lenguaje de tipo imperativo donde se utilizó por primera vez el vocabulario que se ha hecho característico de este tipo de lenguajes. Un lenguaje que se ha añadido a los llamados orientados a objetos es la extensión de C aparecida en 1986, C++. Aunque es muy diferente a Smalltalk, ha sido muy popular por ser una extensión del lenguaje C. Uno de los últimos lenguajes OO aparecidos es Java, al que dedicamos el siguiente apartado.

1.5 El lenguaje de programación Java

Java es una tecnología que hace sencilla la construcción de aplicaciones distribuidas, programas que son ejecutables por múltiples ordenadores a través de la red. En el estado del arte en la programación de red, Java promete extender el papel de Internet desde el terreno de las comunicaciones hacia una red en la cual puedan ejecutarse las aplicaciones completas. Su novedosa tecnología permitirá a los negocios proporcionar servicios de transacción a gran escala y en tiempo real y contener informacion interactiva en Internet. Java simplifica también la construcción de agentes software, programas que se mueven a través de la red y desempeñan funciones en ordenadores remotos en nombre del usuario. En un futuro cercano, los usuarios podrán enviar agentes software desde sus PCs hacia Internet para localizar información específica o para realizar transacciones en el menor tiempo posible en cualquier lugar del mundo.

Java llevará estos adelantos todavía más lejos haciendo posible suministrar aplicaciones completamente interactivas vía Web. Las razones por las cuales se ha prestado tanta atención al lenguaje Java podrían resumirse en la siguiente lista de posibilidades que Java ofrece a sus usuarios:

En particular, los programas Java se pueden incrustar en documentos Web, convirtiendo páginas estáticas en aplicaciones que se ejecutan en el ordenador del usuario.

1.5.1 Una Breve Historia sobre Java

En 1990, Sun Microsystems comenzó un proyecto llamado Green para desarrollar software destinado a electrónica de consumo. James Gosling, un veterano en el diseño de software de red, fue asignado al nuevo proyecto. Gosling comenzó a escribir software en C++ para utilizarlo en aparatos como tostadoras, videos, etc. Este software se utilizó para crear electrodomésticos más inteligentes, añadiéndoles displays digitales o utilizando inteligencia artificial para controlar mejor los mecanismos. Sin embargo, pronto se dio cuenta de que C++ era demasiado susceptible a errores que pueden detener el sistema. Y aunque todo el mundo está acostumbrado a que el ordenador se cuelgue, nadie espera que su tostadora deje de funcionar.

La solución de Gosling a este problema fue un nuevo lenguaje llamado Oak. Oak mantuvo una sintaxis similar a C++ pero omitiendo las características potencialmente peligrosas. Para conseguir que fuese un lenguaje de programación de sistemas controladores eficiente, Oak necesitaba poder responder a eventos provenientes del exterior en cuestión de microsegundos. Tambén era necesario que fuese portátil; esto es, que fuese capaz de ejecutarse en un determinado número de chips y entornos diferentes. Esta independencia del hardware podría proporcionar al fabricante de una tostadora cambiar el chip que utiliza para hacerla funcionar sin necesidad de cambiar el software. El fabricante podría utilizar también partes del mismo código que utiliza la tostadora para hacer funcionar un horno. Esto podría reducir costes, tanto de desarrollo como de hardware, aumentando también su fiabilidad.

Al mismo tiempo que Oak maduraba, la WWW se encontraba en su periodo de crecimiento y el equipo de desarrollo de Sun se dio cuenta de que Oak era prefectamente adecuado para la programación en Internet. En 1994 completaron su trabajo en un producto conocido como WebRunner, un primitivo visor Web escrito en Oak. WebRunner se renombró posteriomente como HotJava y demostró el poder de Oak como herramienta de desarrollo en Internet.

Finalmente, en 1995, Oak se renombró como Java por razones de marketing y fue presentado en Sun World 1995. Incluso antes de la primera distribución del compilador de Java en Junio de 1996, Java ya era considerado como el estándar en la industria para la programación en Internet.

1.5.2 La Arquitectura Java

La fortaleza de Java reside precisamente en su arquitectura única. Los diseñadores de Java necesitaban un lenguaje que fuera, sobre todo, sencillo de utilizar para el programador. A pesar de todo, y con el propósito de crear aplicaciones de red eficientes, Java necesitaba también la posibilidad de ejecutarse de forma segura en la red y trabajar en una amplísima gama de plataformas. Java cumple todos estos puntos y muchos más.

Cómo trabaja Java

Como muchos otros lenguajes de programación, Java utiliza un compilador para convertir el código fuente, legible para el ser humano, en programas ejecutables. Los compiladores tradicionales genera código que puede ejecutarse únicamente por un hardware específico. Los compiladores Java generan código binario o bytecode independiente de la arquitectura. Estos bytecodes se ejecutarán exclusivamente en una Máquina Virtual Java, Virtual Machine, VM, un procesador Java idealizado que normalmente se implementa por software, aunque la VM se ha implementado también como un chip hardware por Sun y otros.

Los archivos binarios Java se denominan archivos de clases debido a que contienen clases simples Java. Para ejecutar bytecodes, la máquina virtual utiliza cargadores de clases para obtener los bytecodes del disco o de la red. Cada archivo de clases se lleva entonces a un verificador de bytecodes que se asegura de que la clase tienen un formato correcto y que no llegará a corromper la memoria cuando se ejecute. Una vez verificados los bytecodes se interpretan por un interprete.

1.6 Bibliografía


Jesús Vegas
Dpto. Informática
Universidad de Valladolid
jvegas@infor.uva.es