En principio, la diferencia entre compiladores y intérpretes parece bastante clara:

  • Un intérprete es un programa que ejecuta directamente las instrucciones escritas en un lenguaje de programación dado
  • Un compilador es un programa que transforma el código fuente de un programa a su equivalente en otro lenguaje de programación de más bajo nivel (una excepción sería el caso de los transpiladores, recordad la diferencia entre compilador y transpilador)

Pero si miramos estas definiciones más de cerca veremos que la diferencia empieza a ser difusa. Por ejemplo, un intérprete podría primero traducir el código a interpretar a alguna representación intermedia para así acelerar su ejecución. Esto es por ejemplo lo que pasa con los lenguajes que se basan en máquinas virtuales, como el caso de Java y su Java Virtual Machine (ver la imagen que encabeza esta entrada).

Si esto es así, podríamos preguntarnos: ¿son todos los lenguajes basados en máquinas virtuales interpretados? o bien se puede decir que ¿son todos compilados?.

Pues, la respuesta es que sí a las dos preguntas: el lenguaje primero se compila a su representación intermedia que luego se interpreta en tiempo de ejecución. En este caso, no hay que pensar en los compiladores e intérpretes como programas individuales sino como componentes de un sistema más complejo que los combina para llegar a ejecutar un programa. Ésta es la tendencia actual.

¿Puedes explicarme todos los detalles técnicos acerca de compiladores e intérpretes?

Si realmente te interesa saber todas las sutilezas de compiladores e intérpretes y cuáles son sus mecanismos internos, lo tienes fácil. “Sólo” tienes que mirar en alguno de estos libros recomendados o leer las apasionadas discusiones que muchos de nuestras colegas tienen en foros como StackOverflow.

Pero no creo que, una vez entendida la diferencia teórica entre los dos (y el hecho que muchas veces forman parte de un puzle más complejo), conocer más detalles internos te sea muy útil. Lo que sí es útil es saber como trabajar con ellos como programadores o incluso como creadores de nuevos lenguajes de programación. Por ejemplo, al diseñar un nuevo lenguaje, ¿que es mejor: crear un compilador o un intérprete para él? o si un lenguaje que estamos usando ofrece tanto una versión compilada como interpretada, ¿cuál es la que más nos conviene?.

Dedicaremos el resto del post a discutir las ventajas e inconvenientes de cada alternativa. Todas ellas derivan de la principal diferencia “práctica” entre compilación e interpretación: el intérprete ejecuta el código en este mismo momento, el compilador lo prepara para su ejecución posterior.

Un intérprete ejecuta el código AHORA. Un compilador lo prepara para ejecutarlo LUEGO Click To Tweet

Compilación vs interpretación: ventajas e inconvenientes

Como distribuir un programa

Un compilador genera un programa “stand-alone” es decir que tiene sentido y se puede ejecutar por sí solo, mientras que un programa interpretado siempre necesita su intérprete correspondiente para poder ejecutarse.

Por lo tanto, es más fácil distribuir un programa compilado. El problema es que sólo podrás ejecutarlo en la plataforma para la que se ha compilado. Cada sistema operativo (y cada familia de procesadores) necesita una versión compilada diferente. Por ejemplo, un programa C++ podría ejecutarse en un ordenador con un procesador x86 pero no en uno con ARM. O podría funcionar en Linux pero no en Windows.

Y al revés, puedes distribuir la misma copia de un programa interpretado a usuarios en plataformas muy diferentes. Pero cada uno de ellos tendrá que disponer de un intérprete para su plataforma. 

Soporte multiplataforma

De lo comentado en el apartado anterior, se deriva también que es más fácil escribir un programa que sea multiplataforma con lenguajes interpretados. De hecho lo que estarás haciendo es escribir el programa para una plataforma específica: el intérprete y dejarás que sean las diferentes versiones del intérprete quienes te den la capacidad de ser multiplataforma.

Tendrás que seguir teniendo en cuenta algunas diferencias (como el carácter separador de directorios) pero serán mínimas ya que la “interfaz” que te da el intérprete esconde muchas de las complejidades y variaciones de cada plataforma.

En cambio, para programas compilados, el programador es responsable de gestionar las diferencias entre plataformas. Esto, en parte, es también debido a que  los lenguajes compilados suelen ser de más bajo nivel, como C++, y permiten un acceso más directo a las funciones del sistema. Otra complejidad añadida es que si dependes de librerías externas, necesitas también que esas librerías soporten también la plataforma para la que quieres compilar. Si no, estás perdido.

Las múltiples cara de la velocidad

Aquí tenemos una paradoja. Un compilador es, al mismo tiempo, más rápido y  más lento que un intérprete. Mucha gente sabe que un programa compilado es más rápido que uno interpretado pero esto es sólo una visión parcial del problema. Un programa compilado es más rápido de ejecutar que uno interpretado pero se tarda más a compilar y ejecutar un programa que a interpretarlo.

Es cierto que un compilador generar programas más rápidos. La clave es que cada sentencia se analiza una sola vez y no una vez en cada ejecución. Esto también permite optimizar el código que se genera aunque esto añade un coste temporal al proceso de compilación, cosa que no nos podemos permitir al interpretar el programa.

Velocidad de ejecución vs Velocidad de desarrollo

Seguro que piensas, si se ejecuta más rápido, ¿a quién le importa que tarde más en compilar?. Ésta es la visión clásica que asume que no importa que los desarrolladores tengamos que sufrir más si el usuario queda contento. Pero si las ganancias en tiempo de ejecución son mínimas, ¿vale realmente la pena la pérdida de productividad durante el desarrollo?

No es lo mismo crear un sistema operativo que una app para el móvil. A veces el tiempo de ejecución es importante. A veces es más importante que se puedan incorporar nuevas funcionalidades aunque la app tarde un poquito más.

Los misterios de la depuración

En principio, depurar programas que van a ser interpretados es más fácil que depurar programas a compilar por las siguientes razones:

  • con el intérprete tienes una sola versión del ejecutable, no necesitas depurar cada versión final por separado.
  • hay menos errores específicos de plataforma cuando se usa un intérprete
  • como el intérprete ejecuta el código al momento, el código fuente sigue estando disponible
  • el intérprete ejecuta las instrucciones una a una (sin oscuras optimizaciones) lo que hace más fácil localizar la fuente del error.

Todo esto es cierto pero en la práctica depende mucho de la herramienta que utilices.  De hecho si te paras a pensarlo, seguramente te es más difícil depurar JavaScript que C++. Primero porqué JavaScript utiliza tipado dinámico mientras que el de C++ es estático, lo que facilita encontrar errores por muy compilado que sea el lenguaje. Pero aún más que esto, la diferencia es que la mayoría de programadores C++ utilizan algún IDE potente al escribir código C (para evitar algunos de los problemas mencionados arriba) mientras que muchos más se atreven a editar código JavaScript “a pelo”

A igualdad de IDEs, los interpretados siempre serán más fáciles de depurar pero una buena herramienta permite depurar sin problemas código de lenguajes compilados.

Resumen

Hemos visto como se comparan los procesos de compilación y interpretación. Más importante todavía, hemos visto que más allá de consideraciones filosóficas, cada opción tiene unas implicaciones directas en el proceso de desarrollo que hay que tener en cuenta.

Si has llegado hasta aquí y quieres dar el siguiente paso y construir tu propio intérprete o compilador, mira mi lista de recursos para crear un lenguaje de programación. Y si lo que quieres es crear tu propio lenguaje, este libro sobre como crear fácilmente tu propio lenguaje es tu mejor opción.

 

Este post es una traducción del post The Difference Between a Compiler and an Interpreter