La concisión es poder

Mayo de 2002

| "La cantidad de significado comprimido en un pequeño espacio por signos algebraicos es otra circunstancia que facilita los razonamientos que estamos acostumbrados a llevar a cabo con su ayuda."

- Charles Babbage, citado en la Conferencia Turing de Iverson

En la discusión sobre los temas planteados por Revenge of the Nerds en la lista de correo LL1, Paul Prescod escribió algo que se me quedó grabado.

El objetivo de Python es la regularidad y la legibilidad, no la concisión.

A primera vista, esto parece algo bastante demoledor que decir sobre un lenguaje de programación. Por lo que puedo decir, concisión = poder. Si es así, entonces sustituyendo, obtenemos

El objetivo de Python es la regularidad y la legibilidad, no el poder.

y esto no parece una compensación (si es que es una compensación) que uno quiera hacer. No está lejos de decir que el objetivo de Python es no ser eficaz como lenguaje de programación.

¿Concisión = poder? Esta me parece una pregunta importante, quizás la pregunta más importante para cualquiera interesado en el diseño de lenguajes, y una que sería útil abordar directamente. Todavía no estoy seguro de que la respuesta sea un simple sí, pero parece una buena hipótesis para empezar.

Hipótesis

Mi hipótesis es que la concisión es poder, o está lo suficientemente cerca como para que, excepto en ejemplos patológicos, se puedan tratar como idénticas.

Me parece que la concisión es para lo que sirven los lenguajes de programación. A los ordenadores les daría igual que se les dijera qué hacer directamente en lenguaje máquina. Creo que la razón principal por la que nos tomamos la molestia de desarrollar lenguajes de alto nivel es para obtener apalancamiento, de modo que podamos decir (y lo que es más importante, pensar) en 10 líneas de un lenguaje de alto nivel lo que requeriría 1000 líneas de lenguaje máquina. En otras palabras, el punto principal de los lenguajes de alto nivel es hacer que el código fuente sea más pequeño.

Si el código fuente más pequeño es el propósito de los lenguajes de alto nivel, y el poder de algo es qué tan bien logra su propósito, entonces la medida del poder de un lenguaje de programación es cuán pequeños hace nuestros programas.

Por el contrario, un lenguaje que no hace que nuestros programas sean pequeños está haciendo un mal trabajo de lo que se supone que deben hacer los lenguajes de programación, como un cuchillo que no corta bien, o una impresión que es ilegible.

Métricas

¿Pequeño en qué sentido, sin embargo? La medida más común del tamaño del código son las líneas de código. Pero creo que esta métrica es la más común porque es la más fácil de medir. No creo que nadie crea realmente que sea la verdadera prueba de la longitud de un programa. Diferentes lenguajes tienen diferentes convenciones sobre cuánto se debe poner en una línea; en C, muchas líneas no tienen nada más que uno o dos delimitadores.

Otra prueba fácil es el número de caracteres en un programa, pero esto tampoco es muy bueno; algunos lenguajes (Perl, por ejemplo) simplemente usan identificadores más cortos que otros.

Creo que una mejor medida del tamaño de un programa sería el número de elementos, donde un elemento es cualquier cosa que sería un nodo distinto si se dibujara un árbol que representara el código fuente. El nombre de una variable o función es un elemento; un número entero o de punto flotante es un elemento; un segmento de texto literal es un elemento; un elemento de un patrón, o una directiva de formato, es un elemento; un nuevo bloque es un elemento. Hay casos límite (¿es -5 dos elementos o uno?) pero creo que la mayoría de ellos son los mismos para todos los lenguajes, por lo que no afectan mucho a las comparaciones.

Esta métrica necesita ser desarrollada, y podría requerir interpretación en el caso de lenguajes específicos, pero creo que intenta medir lo correcto, que es el número de partes que tiene un programa. Creo que el árbol que se dibujaría en este ejercicio es lo que hay que hacerse en la cabeza para concebir el programa, y por lo tanto su tamaño es proporcional a la cantidad de trabajo que hay que hacer para escribirlo o leerlo.

Diseño

Este tipo de métrica nos permitiría comparar diferentes lenguajes, pero ese no es, al menos para mí, su valor principal. El valor principal de la prueba de concisión es como guía en el diseño de lenguajes. La comparación más útil entre lenguajes es entre dos variantes potenciales del mismo lenguaje. ¿Qué puedo hacer en el lenguaje para hacer los programas más cortos?

Si la carga conceptual de un programa es proporcional a su complejidad, y un programador dado puede tolerar una carga conceptual fija, entonces esto es lo mismo que preguntar, ¿qué puedo hacer para permitir que los programadores hagan más? Y eso me parece idéntico a preguntar, ¿cómo puedo diseñar un buen lenguaje?

(Por cierto, nada hace más patentemente obvio que el viejo cliché "todos los lenguajes son equivalentes" es falso que diseñar lenguajes. Cuando estás diseñando un nuevo lenguaje, estás constantemente comparando dos lenguajes —el lenguaje si hiciera x, y si no lo hiciera— para decidir cuál es mejor. Si esta fuera realmente una pregunta sin sentido, podrías lanzar una moneda.)

Apuntar a la concisión parece una buena manera de encontrar nuevas ideas. Si puedes hacer algo que haga más cortos muchos programas diferentes, probablemente no sea una coincidencia: probablemente has descubierto una nueva abstracción útil. Incluso podrías escribir un programa para ayudar buscando patrones repetidos en el código fuente. Entre otros lenguajes, aquellos con reputación de concisión serían los que buscar para nuevas ideas: Forth, Joy, Icon.

Comparación

La primera persona que escribió sobre estos temas, hasta donde yo sé, fue Fred Brooks en The Mythical Man Month. Escribió que los programadores parecían generar aproximadamente la misma cantidad de código por día independientemente del lenguaje. Cuando leí esto por primera vez en mis veintes, fue una gran sorpresa para mí y pareció tener enormes implicaciones. Significaba que (a) la única forma de escribir software más rápido era usar un lenguaje más conciso, y (b) alguien que se tomara la molestia de hacerlo podría dejar atrás a los competidores que no lo hicieran.

La hipótesis de Brooks, si es cierta, parece estar en el corazón mismo del hacking. En los años transcurridos, he prestado mucha atención a cualquier evidencia que pudiera obtener sobre la cuestión, desde estudios formales hasta anécdotas sobre proyectos individuales. No he visto nada que lo contradiga.

Todavía no he visto evidencia que me parezca concluyente, y no espero verla. Estudios como la comparación de lenguajes de programación de Lutz Prechelt, aunque generan el tipo de resultados que esperaba, tienden a usar problemas demasiado cortos para ser pruebas significativas. Una mejor prueba de un lenguaje es lo que sucede en programas que tardan un mes en escribirse. Y la única prueba real, si crees como yo que el propósito principal de un lenguaje es ser bueno para pensar (en lugar de solo para decirle a un ordenador qué hacer una vez que se te ha ocurrido), es qué cosas nuevas puedes escribir en él. Por lo tanto, cualquier comparación de lenguajes en la que tengas que cumplir una especificación predefinida está probando ligeramente lo incorrecto.

La verdadera prueba de un lenguaje es qué tan bien puedes descubrir y resolver nuevos problemas, no qué tan bien puedes usarlo para resolver un problema que alguien más ya ha formulado. Estas dos son criterios bastante diferentes. En el arte, medios como el bordado y el mosaico funcionan bien si sabes de antemano lo que quieres hacer, pero son absolutamente pésimos si no lo sabes. Cuando quieres descubrir la imagen mientras la creas —como tienes que hacer con algo tan complejo como la imagen de una persona, por ejemplo— necesitas usar un medio más fluido como el lápiz, la aguada o la pintura al óleo. Y, de hecho, la forma en que se hacen los tapices y los mosaicos en la práctica es hacer primero una pintura y luego copiarla. (La palabra "cartón" se usaba originalmente para describir una pintura destinada a este propósito).

Lo que esto significa es que es poco probable que tengamos comparaciones precisas de la potencia relativa de los lenguajes de programación. Tendremos comparaciones precisas, pero no exactas. En particular, los estudios explícitos con el propósito de comparar lenguajes, porque probablemente usarán problemas pequeños y necesariamente usarán problemas predefinidos, tenderán a subestimar el poder de los lenguajes más potentes.

Los informes del campo, aunque necesariamente serán menos precisos que los estudios "científicos", probablemente serán más significativos. Por ejemplo, Ulf Wiger de Ericsson realizó un estudio que concluyó que Erlang era 4-10 veces más conciso que C++, y proporcionalmente más rápido para desarrollar software:

Las comparaciones entre proyectos de desarrollo internos de Ericsson indican una productividad similar en líneas/hora, incluyendo todas las fases del desarrollo de software, independientemente del lenguaje (Erlang, PLEX, C, C++ o Java) que se utilizara. Lo que diferencia a los distintos lenguajes es entonces el volumen del código fuente.

El estudio también aborda explícitamente un punto que solo era implícito en el libro de Brooks (ya que medía líneas de código depurado): los programas escritos en lenguajes más potentes tienden a tener menos errores. Eso se convierte en un fin en sí mismo, posiblemente más importante que la productividad del programador, en aplicaciones como los conmutadores de red.

La Prueba del Gusto

En última instancia, creo que tienes que confiar en tu instinto. ¿Cómo se siente programar en el lenguaje? Creo que la forma de encontrar (o diseñar) el mejor lenguaje es volverse hipersensible a qué tan bien un lenguaje te permite pensar, luego elegir/diseñar el lenguaje que se sienta mejor. Si alguna característica del lenguaje es torpe o restrictiva, no te preocupes, lo sabrás.

Tal hipersensibilidad tendrá un costo. Descubrirás que no puedes soportar programar en lenguajes torpes. Encuentro insoportablemente restrictivo programar en lenguajes sin macros, al igual que alguien acostumbrado a la tipificación dinámica encuentra insoportablemente restrictivo tener que volver a programar en un lenguaje donde tienes que declarar el tipo de cada variable, y no puedes hacer una lista de objetos de diferentes tipos.

No soy el único. Conozco a muchos hackers de Lisp a los que les ha pasado esto. De hecho, la medida más precisa del poder relativo de los lenguajes de programación podría ser el porcentaje de personas que conocen el lenguaje que aceptarán cualquier trabajo donde puedan usar ese lenguaje, independientemente del dominio de la aplicación.

Restrictividad

Creo que la mayoría de los hackers saben lo que significa que un lenguaje se sienta restrictivo. ¿Qué está sucediendo cuando sientes eso? Creo que es la misma sensación que tienes cuando la calle que quieres tomar está bloqueada y tienes que tomar un largo desvío para llegar a donde querías ir. Hay algo que quieres decir, y el lenguaje no te lo permite.

Lo que realmente está sucediendo aquí, creo, es que un lenguaje restrictivo es uno que no es lo suficientemente conciso. El problema no es simplemente que no puedas decir lo que planeaste. Es que el desvío que el lenguaje te obliga a tomar es más largo. Prueba este experimento mental. Supongamos que hay algún programa que querías escribir, y el lenguaje no te dejaba expresarlo como planeaste, sino que te obligaba a escribir el programa de alguna otra manera que fuera más corta. Para mí, al menos, eso no se sentiría muy restrictivo. Sería como si la calle que querías tomar estuviera bloqueada, y el policía en la intersección te dirigiera a un atajo en lugar de un desvío. ¡Genial!

Creo que la mayor parte (¿noventa por ciento?) de la sensación de restrictividad proviene de ser forzado a hacer que el programa que escribes en el lenguaje sea más largo que uno que tienes en tu cabeza. La restrictividad es en su mayoría falta de concisión. Así que cuando un lenguaje se siente restrictivo, lo que eso (en su mayoría) significa es que no es lo suficientemente conciso, y cuando un lenguaje no es conciso, se sentirá restrictivo.

Legibilidad

La cita con la que comencé menciona otras dos cualidades, regularidad y legibilidad. No estoy seguro de qué es la regularidad, o qué ventaja, si es que alguna, tiene el código que es regular y legible sobre el código que es meramente legible. Pero creo que sé lo que se entiende por legibilidad, y creo que también está relacionado con la concisión.

Tenemos que tener cuidado aquí para distinguir entre la legibilidad de una línea individual de código y la legibilidad de todo el programa. Es la segunda la que importa. Estoy de acuerdo en que una línea de Basic es probablemente más legible que una línea de Lisp. Pero un programa escrito en Basic tendrá más líneas que el mismo programa escrito en Lisp (especialmente una vez que cruzas a Greenspunland). El esfuerzo total de leer el programa Basic seguramente será mayor.

esfuerzo total = esfuerzo por línea x número de líneas

No estoy tan seguro de que la legibilidad sea directamente proporcional a la concisión como lo estoy de que el poder lo sea, pero ciertamente la concisión es un factor (en el sentido matemático; ver ecuación anterior) en la legibilidad. Así que puede que ni siquiera tenga sentido decir que el objetivo de un lenguaje es la legibilidad, no la concisión; podría ser como decir que el objetivo era la legibilidad, no la legibilidad.

Lo que significa legibilidad por línea, para el usuario que se encuentra con el lenguaje por primera vez, es que el código fuente se verá intimidante. Por lo tanto, la legibilidad por línea podría ser una buena decisión de marketing, aunque sea una mala decisión de diseño. Es isomorfa a la técnica muy exitosa de permitir a las personas pagar a plazos: en lugar de asustarlos con un alto precio inicial, les dices el bajo pago mensual. Sin embargo, los planes de pago a plazos son una pérdida neta para el comprador, al igual que la mera legibilidad por línea probablemente lo sea para el programador. El comprador va a hacer un montón de esos pagos bajos, bajos; y el programador va a leer un montón de esas líneas individualmente legibles.

Esta compensación precede a los lenguajes de programación. Si estás acostumbrado a leer novelas y artículos de periódicos, tu primera experiencia leyendo un artículo de matemáticas puede ser desalentadora. Podría llevar media hora leer una sola página. Y sin embargo, estoy bastante segura de que la notación no es el problema, aunque pueda parecerlo. El artículo de matemáticas es difícil de leer porque las ideas son difíciles. Si expresaras las mismas ideas en prosa (como tuvieron que hacer los matemáticos antes de evolucionar notaciones concisas), no serían más fáciles de leer, porque el artículo crecería al tamaño de un libro.

¿Hasta qué punto?

Varias personas han rechazado la idea de que concisión = poder. Creo que sería más útil, en lugar de simplemente argumentar que son lo mismo o no, preguntar: ¿hasta qué punto la concisión = poder? Porque claramente la concisión es una gran parte de para qué sirven los lenguajes de alto nivel. Si no es para eso todo, ¿para qué más sirven, y cuán importantes son, relativamente, estas otras funciones?

No propongo esto solo para hacer el debate más civilizado. Realmente quiero saber la respuesta. ¿Cuándo, si es que alguna vez, un lenguaje es demasiado conciso para su propio bien?

La hipótesis con la que comencé fue que, excepto en ejemplos patológicos, pensé que la concisión podría considerarse idéntica al poder. Lo que quise decir fue que en cualquier lenguaje que alguien diseñara, serían idénticos, pero que si alguien quisiera diseñar un lenguaje explícitamente para refutar esta hipótesis, probablemente podría hacerlo. De hecho, ni siquiera estoy seguro de eso.

Lenguajes, no Programas

Debemos tener claro que estamos hablando de la concisión de los lenguajes, no de los programas individuales. Ciertamente es posible que los programas individuales se escriban con demasiada densidad.

Escribí sobre esto en On Lisp. Una macro compleja puede tener que ahorrar muchas veces su propia longitud para justificarse. Si escribir una macro complicada pudiera ahorrarte diez líneas de código cada vez que la usas, y la macro en sí misma tiene diez líneas de código, entonces obtienes un ahorro neto en líneas si la usas más de una vez. Pero eso aún podría ser un movimiento en falso, porque las definiciones de macros son más difíciles de leer que el código ordinario. Podrías tener que usar la macro diez o veinte veces antes de que produzca una mejora neta en la legibilidad.

Estoy seguro de que cada lenguaje tiene tales compensaciones (aunque sospecho que las apuestas aumentan a medida que el lenguaje se vuelve más potente). Cada programador debe haber visto código que alguna persona inteligente ha hecho marginalmente más corto usando trucos de programación dudosos.

Así que no hay discusión sobre eso, al menos no de mi parte. Los programas individuales ciertamente pueden ser demasiado concisos para su propio bien. La pregunta es, ¿puede serlo un lenguaje? ¿Puede un lenguaje obligar a los programadores a escribir código que sea corto (en elementos) a expensas de la legibilidad general?

Una razón por la que es difícil imaginar que un lenguaje sea demasiado conciso es que si hubiera una forma excesivamente compacta de expresar algo, probablemente también habría una forma más larga. Por ejemplo, si sintieras que los programas de Lisp que usan muchas macros o funciones de orden superior son demasiado densos, podrías, si lo prefieres, escribir código que sea isomorfo a Pascal. Si no quieres expresar el factorial en Arc como una llamada a una función de orden superior (rec zero 1 * 1-) también puedes escribir una definición recursiva: (rfn fact (x) (if (zero x) 1 (* x (fact (1- x))))) Aunque no se me ocurre ningún ejemplo en este momento, estoy interesado en la pregunta de si un lenguaje podría ser demasiado conciso. ¿Existen lenguajes que te obliguen a escribir código de una manera que sea críptica e incomprensible? Si alguien tiene ejemplos, me interesaría mucho verlos.

(Recordatorio: Lo que busco son programas que sean muy densos según la métrica de "elementos" esbozada anteriormente, no simplemente programas que sean cortos porque se pueden omitir los delimitadores y todo tiene un nombre de un carácter.)