¿Qué es un lenguaje de programación? (Parte 1)
Aunque pudiera parecer todo lo contrario, los ordenadores son máquinas bastante tontas. O mejor dicho, bastante simples, ya que solo “entienden” instrucciones muy sencillas, del estilo “suma estos dos números” o “dime qué número es mayor”; esto es lo que denominamos lenguaje máquina. La gran ventaja del lenguaje máquina es que sus sencillas operaciones pueden ejecutarse a toda velocidad en el procesador de un ordenador.
En los albores de la informática, las programadoras – y escribo en femenino porque eran en su mayoría mujeres – tenían el penoso trabajo de escribir en ese lenguaje. Pensemos un momento en la atención al detalle requerida para escribir un programa que llevase a un hombre al espacio únicamente con esta herramienta. Es como si para indicar a alguien cómo ir de una calle a otra, en vez de utilizar términos como “avanza hasta la intersección y gira a la derecha” tuvieses que decir “sube la pierna derecha, sube la pierna izquierda, repite esto 10 veces, gira tu cuerpo 90 grados a la derecha, …”.
Huelga decir que los seres humanos somos terribles en ese nivel de detalle; mantener únicamente el lenguaje máquina habría condenado a los ordenadores a ser usados únicamente por especialistas. Pero a principios de la década de 1950 la estadounidense Grace Hopper – que no sólo fue programadora sino también militar de la Armada de su país – tuvo una brillante idea: ¿por qué no escribir un programa que tradujese órdenes escritas en inglés al lenguaje máquina? Y así nació la idea de un compilador.
En el mundo de la informática, se denomina compilador a todo aquel programa que traduce de un lenguaje de programación a otro. La dirección más habitual es compilar un lenguaje cercano al humano, comúnmente denominados lenguajes de alto nivel con lenguaje máquina, aunque en la actualidad se pueden encontrar también compiladores entre diferentes lenguajes de alto nivel. Siguiendo con nuestro ejemplo anterior, nosotros escribimos “avanza hasta la intersección” y el compilador lo traduce a la secuencia de movimientos necesaria.
Nombre, variables y funciones
La característica más importante de los lenguajes de programación de alto nivel es que proveen abstracciones que evitan tener que lidiar con detalles nimios. Los diferentes lenguajes ofrecen diferentes niveles y formas de abstracción. Sin embargo, casi todos comparten unas ideas generales.
Dentro del ordenador, la localización de los datos se expresa como lugares en el procesador denominados registros, en la memoria, o en algún dispositivo externo. Pero desde el punto de vista humano, una instrucción como “divide el valor en el registro A entre el valor en la posición de memoria 42” no da ninguna pista de qué está ocurriendo. Por ello, los lenguajes de programación permiten nombrar los elementos que componen el programa; si llamamos al registro A “distancia” y a la mencionada posición de memoria “tiempo”, nos es más sencillo entender que el programa está calculando una velocidad. Un nombre que hace referencia a un dato se denomina comúnmente una variable, y así los programadores hablan de “declarar una variable” cuando quieren decir “dar nombre a un lugar que contiene un dato” o “asignar una variable” cuando quieren decir “dar un valor concreto a ese dato”.
Volvamos de nuevo a nuestro ejemplo. Si estamos desarrollando un programa de navegación, es probable que muchas veces tengamos que realizar la misma serie de instrucciones para comprobar el nombre de una calle. Una posibilidad es copiar y pegar el código que realiza esa tarea cada vez que la necesitamos. Pero una mejor solución es dar un nombre a toda una secuencia de instrucciones; es lo que denominamos una función, procedimiento o subrutina (existen ligeras diferencias entre unas y otras, pero no son importantes en ese punto).
Usar funciones tiene dos ventajas fundamentales: en primer lugar, facilita la comprensión del programa al documentar el propósito de una serie de instrucciones. Pero además, nos permite corregir de forma centralizada el programa: si descubrimos un fallo en la forma en la que comprobamos el nombre de una calle, sólo tenemos que corregirlo en la función, y no en cada lugar donde la usamos.
Portabilidad
Aparte de su enorme complejidad, existe otro problema con el lenguaje máquina: no existe un único “lenguaje máquina”, sino que cada nuevo modelo de procesador introduce variaciones respecto a los demás. Si bien es posible que distintos modelos compartan gran parte de su lenguaje máquina (entonces hablamos de que tienen la misma arquitectura), esto significa que un programador tendría que aprender un nuevo lenguaje cada vez que un procesador entra en el mercado. Visto de otro modo, es como si con cada nueva escoba tuviésemos que aprender a barrer de nuevo.
La introducción de los compiladores como intermediarios entre el lenguaje de alto nivel y la máquina solventa gran parte de este problema: podemos crear varios compiladores que entiendan el mismo lenguaje en un extremo de la comunicación pero produzcan diferentes lenguajes máquina. Así no necesitamos nada más que un grupo pequeño de expertos para mantener los compiladores al día, y la mayor parte de los programadores pueden ignorar los vaivenes del mercado y continuar escribiendo de la misma manera. Eso explica que lenguajes que ya existían en los años 1970, como C o Lisp, puedan seguir usándose en la actualidad.
Durante este artículo hemos hablado de lenguajes de programación, en plural. Y es que del mismo modo que el lenguaje humano permite infinitas variaciones, así lo hacen los de programación. En la próxima entrega intentaremos describir a grandes rasgos cuáles son las familias más importantes de lenguajes; como ya hemos introducido más arriba, las diferencias están en las abstracciones que éstos ofrecen.