LA VENGANZA DE LOS NERDS


Mayo 2002
 
"Estábamos tras los programadores de C++. Nos las arreglamos para llevar a muchos de ellos casi a mitad de camino hacia Lisp."

- Guy Steele, co-autor de la especificación Java



En el negocio del software existe una lucha constante entre los académicos de cabeza puntiaguda, y otra fuerza igualmente formidable, los jefes de pelo puntiagudo. Todo el mundo sabe quién es el jefe de pelo puntiagudo, ¿no es así? Creo que la mayoría de las personas en el mundo de la tecnología no sólo reconocen a este personaje de dibujos animados, sino que conocen a la persona real en su empresa sobre el que fue modelado.

El jefe de pelo puntiagudo combina milagrosamente dos cualidades que son comunes por sí mismas, pero que rara vez se ven juntas: (a) no sabe nada en absoluto acerca de tecnología, y (b) tiene opiniones muy fuertes al respecto.

Supongamos, por ejemplo, que necesitas escribir una pieza de software. El jefe de pelo puntiagudo no tiene idea de cómo debe funcionar este software, y no distingue un lenguaje de programación de otro, y sin embargo él sabe en que lenguaje tienes que escribirlo. Exacto. Él cree que tienes que escribirlo en Java.

¿Por qué piensa eso? Echemos un vistazo al interior del cerebro del jefe de pelo puntiagudo. Lo que está pensando es algo como esto: Java es un estándar. Sé que debe serlo, porque lo leo en la prensa todo el tiempo. Ya que es un estándar, no me meteré en problemas por usarlo. Y eso significa también que siempre habrá muchos programadores Java, así que si los programadores que ahora trabajan para mí renuncian, como misteriosamente siempre lo hacen, podré reemplazarlos fácilmente.

Bien, esto no suena tan descabellado. Pero todo esta basado en una suposición que se da por hecho, y esa suposición resulta ser falsa. El jefe de pelo puntiagudo cree que todos los lenguajes de programación son más o menos equivalentes. Si eso fuera cierto, daría justo en el blanco. Si todos los lenguajes son equivalentes, por supuesto, utiliza el lenguaje que todos los demás estén usando.

Sin embargo, no todos los lenguajes son equivalentes, y creo que puedo probarte esto sin mencionar siquiera las diferencias entre ellos. Si le hubieras preguntado al jefe de pelo puntiagudo en 1992 en que lenguaje debería escribirse el software, habría contestado con tan poca vacilación como lo hace hoy en día. El software debe escribirse en C++. Pero si todos los lenguajes son equivalentes, ¿por qué la opinión del jefe de pelo puntiagudo cambia? De hecho, ¿por qué los desarrolladores de Java se han molestado en crear un nuevo lenguaje?

Es de suponer que, si se crea un nuevo lenguaje, es porque crees que es mejor de alguna manera a lo que la gente ya tenía. Y de hecho, Gosling [a] deja claro en el primer documento de Java que Java fue diseñado para solucionar algunos problemas con C++. Así que ahí lo tienes: los lenguajes no son todos equivalentes. Si sigues la pista a través del cerebro del jefe de pelo puntiagudo a Java y luego a través de la historia de Java hasta sus orígenes, acabas teniendo una idea que contradice la suposición con la que empezaste.

Entonces, ¿quién tiene la razón? ¿James Gosling, o el jefe de pelo puntiagudo? No es de extrañar que Gosling esté en lo correcto. Algunos lenguajes son mejores, para ciertos problemas, que otros. ¿Y sabes? eso plantea algunas preguntas interesantes. Java fue diseñado para ser mejor, en ciertos problemas, que C++. ¿Qué problemas? ¿Cuándo es mejor Java, y cuando lo es C++? ¿Existen situaciones donde otros lenguajes son mejores que cualquiera de ellos?

Una vez que empiezas a considerar esta cuestión, has abierto una verdadera caja de Pandora. Si el jefe de pelo puntiagudo tuviera que pensar el problema en toda su complejidad, su cerebro explotaría. En tanto considere todos los lenguajes equivalentes, lo único que tiene que hacer es elegir el que parezca tener mayor impulso, y ya que es más una cuestión de moda que de tecnología, incluso él puede probablemente dar con la respuesta correcta. Pero si los lenguajes varían, de pronto tiene que resolver dos ecuaciones simultáneas, tratando de encontrar un equilibrio óptimo entre dos cosas de las que no sabe nada: la conveniencia relativa de una veintena de los principales lenguajes para el problema que necesita resolver, y las probabilidades de encontrar programadores, bibliotecas, etc., para cada uno. Si eso es lo que está al otro lado de la puerta, no es de extrañar que el jefe de pelo puntiagudo no quiera abrirla.

La desventaja de creer que todos los lenguajes de programación son equivalentes es que no es cierto. Pero la ventaja es que te hace la vida mucho más simple. Y creo que esa es la razón principal por la que la idea esta tan extendida. Es una idea cómoda.

Sabemos que Java debe ser bastante bueno, porque es el nuevo y atractivo lenguaje de la programación. ¿Lo es? Si contemplamos el mundo de los lenguajes de programación desde la distancia, parece que Java es lo último. (De suficientemente lejos, todo lo que puedes ver es un gran anuncio luminoso pagado por Sun.) Pero si nos fijamos en este mundo de cerca, te encuentras con que hay grados de lo que se considera atractivo. Dentro de la subcultura hacker, hay otro lenguaje llamado Perl que se considera mucho más atractivo que el de Java. Slashdot, por ejemplo, es generado por Perl. No creo que encuentres a estos chicos usando Java Server Pages. Pero hay otro lenguaje, más nuevo, llamado Python, cuyos usuarios tienden a mirar hacia abajo a Perl, y más esperando en las alas.

Si te fijas en estos lenguajes por orden, Java, Perl, Python, notarás un patrón interesante. Lo notarás, siempre y cuando seas un hacker Lisp. Cada uno de estos se asemeja cada vez más a Lisp. Python copia incluso características que muchos hackers de Lisp consideran errores. Podrías trasladar a Python programas Lisp sencillos línea por línea. Es 2002, y los lenguajes de programación casi se han puesto al día con 1958.
 

Poniéndose al Día con las Matemáticas


Lo que quiero decir es que Lisp fue descubierto por John McCarthy [b] en 1958, y los lenguajes de programación populares están apenas ahora poniendose al corriente con las ideas que entonces desarrolló.

Ahora, ¿cómo podría ser eso cierto? ¿No es acaso la tecnología informática algo que cambia muy rápidamente? Quiero decir, en 1958 las computadoras eran gigantes del tamaño de un refrigerador con la potencia de procesamiento de un reloj de pulsera. ¿Cómo podría cualquier tecnología tan antigua ser relevante, y mucho menos superior a los últimos adelantos?

Te diré cómo. Se debe a que Lisp no estaba diseñado para ser un lenguaje de programación, por lo menos no en el sentido que lo entendemos hoy en día. Lo qué queremos decir por lenguaje de programación se refiere a algo que usamos para decirle a una computadora qué hacer. Eventualmente McCarthy tuvo la intención de desarrollar un lenguaje de programación en este sentido, pero el Lisp con el que en realidad terminamos se basa en algo distinto que hizo como un ejercicio teórico— un esfuerzo por definir una alternativa más conveniente para la máquina de Turing. Como McCarthy, dijo más tarde,

Otra manera de mostrar que Lisp era más ordenado que las máquinas de Turing era escribir una función universal Lisp y demostrar que es más breve y comprensible que la descripción de una máquina universal de Turing. Esta fue la función de Lisp eval ..., que calcula el valor de una expresión Lisp... Escribir eval requería inventar una notación que representara las funciones de Lisp como datos de Lisp, y tal notación fue ideada para los fines del documento, sin pensar que sería utilizada para expresar los programas de Lisp en la práctica.


Lo que sucedió después fue que, en algún momento a finales de 1958, Steve Russell, uno de los estudiantes de posgrado de McCarthy, observó esta definición de eval y se dio cuenta de que si lo traducía a lenguaje de máquina, el resultado sería un intérprete de Lisp.

Esto fue una gran sorpresa en su momento. Aqui esta lo que McCarthy dijo al respecto más adelante en una entrevista:

Steve Russell dijo: —Mira, ¿por qué no programo esta eval ..., y yo le dije: —¡Ja!, ¡ja!, estás confundiendo la teoría con la práctica, esta eval está destinada para la lectura, no para la computación. Pero él siguió adelante y lo hizo. Es decir, compiló la función eval de mi articulo en código de la máquina [IBM] 704, corrigiendo errores, y luego anunció esto como un intérprete de Lisp, que sin duda lo era. Así que en ese momento Lisp tenía esencialmente la forma que tiene hoy en día...


De repente, en cuestión de semanas, creo, McCarthy encontró su ejercicio teórico transformado en un lenguaje de programación real— y uno más poderoso de lo que él había previsto.

Por lo que la breve explicación de por qué este lenguaje de la década de 1950 no es obsoleto, es que no era tecnología, sino matemáticas y las matemáticas no envejecen. No es el hardware de la década de 1950 con lo que hay que comparar a Lisp, sino, por ejemplo, con el algoritmo Quicksort [c], que fue descubierto en 1960 y sigue siendo el más rápido del tipo de propósito general.

Hay otro lenguaje de la década de 1950 que aún sobrevive: Fortran, y representa el punto de vista opuesto al diseño del lenguaje. Lisp fue una pieza teórica que inesperadamente se convirtió en un lenguaje de programación. Fortran fue desarrollado intencionalmente como lenguaje de programación, pero lo que hoy consideraríamos uno de muy bajo nivel.

Fortran I, el lenguaje que se desarrolló en 1956, era un animal muy diferente del actual Fortran. Fortran I era más o menos un lenguaje de ensamblaje con matemáticas. En cierto modo, era menos potente que lenguajes de ensamblaje más recientes; por ejemplo, no tenía subrutinas, sólo ramas. El Fortran de hoy en día esta probablemente más cercano a Lisp que a Fortran I.

Lisp y Fortran eran los troncos de dos árboles separados evolutivamente, uno con raíces en las matemáticas y otro con raíces en arquitectura de máquina. Estos dos árboles han ido convergiendo desde entonces. Lisp comenzó poderoso, y en los siguientes veinte años se hizo rápido. Los llamados lenguajes principales comenzaron rápidos, y durante los siguientes cuarenta años se hicieron gradualmente más potentes, apenas ahora el más avanzado de ellos están bastante cerca de Lisp. Cerca, pero todavía les faltan algunas cosas...
 

Lo qué Hizo Diferente a Lisp


Cuando recien se desarrolló, Lisp incorporaba nueve ideas nuevas. Algunas de estas ahora las damos por sentado, las demás sólo se ven en los lenguajes más avanzados, y dos siguen siendo únicas a Lisp. Los nueve ideas son, en orden de su adopción popular:
  1. Condicionales. Una condicional es una construcción if-then-else. Ahora las damos por hecho, pero Fortran I no las tenia. Sólo tenía un condicional goto estrechamente basado en la instrucción de la máquina subyacente.
  1. Un tipo de función. En Lisp, las funciones son un tipo de datos como números enteros o cadenas. Estos tienen una representación literal, se pueden almacenar en las variables, se pueden pasar como argumentos, y así sucesivamente.
  1. Recursividad. Lisp fue el primer lenguaje de programación en soportarla.
  1. Tipado dinámico. En Lisp, todas las variables son efectivamente punteros. Los valores son los que tienen tipos, no variables, y asignar o unir variables significa copiar punteros, no a lo que apuntan.
  1. Recolección de basura.
  1. Programas compuestos por expresiones. Los programas de Lisp son árboles de expresiones, cada una de las cuales devuelve un valor. Esto está en contraste con Fortran y la mayoría de lenguajes sucesivos, que distinguen entre expresiones y declaraciones. Era natural tener esta distinción en Fortran I, por que no puedes anidar declaraciones. Así, mientras que necesitabas expresiones para que las matemáticas funcionaran, no tenía sentido hacer que cualquier otra cosa devolviera un valor, porque no habría nada esperándolo. Esta limitación desapareció con la llegada de los lenguajes estructurados en bloques, pero para entonces ya era demasiado tarde. La distinción entre expresiones y declaraciones se había afianzado. Se transmitió de Fortran a Algol y luego a ambos de sus descendientes.
  1. Un tipo de símbolo. Los símbolos son efectivamente punteros a cadenas almacenadas en una tabla hash. Para que puedas probar la igualdad comparando un puntero, en lugar de comparar cada carácter.
  1. Una notación para el código utilizando árboles de símbolos y constantes.
  1. Todo el lenguaje siempre está disponible. No hay distinción real entre tiempo de lectura, tiempo de compilación y tiempo de ejecución. Puedes compilar o ejecutar código durante la lectura, leer o ejecutar código durante la compilación, y leer o compilar código en tiempo de ejecución.
     
    Ejecutar código en tiempo de lectura le permite a los usuarios reprogramar la sintaxis de Lisp; ejecutar código en tiempo de compilación es la base de las macros; compilar en tiempo de ejecución es la base del uso de Lisp como lenguaje de extensión en programas como Emacs; y la lectura en tiempo de ejecución permite a los programas comunicarse utilizando expresiones-S, una idea reinventada recientemente como XML.
 
Cuando Lisp apareció por vez primera, estas ideas estaban muy alejadas de la práctica ordinaria de la programación, la cuál estaba dictada en gran medida por el hardware disponible a finales de 1950. Con el tiempo, el lenguaje por defecto, plasmado en una serie de lenguajes populares, ha ido evolucionando hacia Lisp. Las ideas de la 1 a la 5 se han generalizado. La número 6 está comenzando a popularizarse. Python se asemeja a la 7, si bien no parece haber ninguna sintaxis para ello.

En cuanto a la número 8, esta puede ser la más interesante de todas. Las ideas 8 y 9 sólo se convirtieron en parte de Lisp por accidente, porque Steve Russell implementó algo que McCarthy no tenía la intención de poner en práctica. Y sin embargo, estas ideas resultan ser responsables tanto de la extraña apariencia de Lisp como de sus características más distintivas. Lisp tiene un aspecto extraño, no tanto porque tenga una sintaxis extraña, sino porque no tiene sintaxis; expresas directamente los programas en los árboles de análisis sintáctico que se construyen en segundo plano cuando se analizan otros lenguajes, y estos árboles están hechos de listas, que son estructuras de datos de Lisp.

Expresar el lenguaje en su propia estructura de datos resulta ser una característica muy potente. Juntas, las ideas 8 y 9 significan que puedes escribir programas que escriben programas. Esta idea puede parecer extraña, pero es una cosa cotidiana en Lisp. La forma más común de hacerlo es con algo llamado macro.

El término "macro" no significa en Lisp lo que significa en otros lenguajes. Una macro Lisp puede ser cualquier cosa, desde una abreviatura a un compilador para un nuevo lenguaje. Si realmente deseas entender Lisp, o simplemente ampliar tus horizontes de programación, deberías aprender más acerca de las macros.

Las macros (en el sentido Lisp) siguen siendo, hasta donde sé, únicas a Lisp. Esto se debe en parte por que para poder tener macros debes, probablemente, hacer que tu lenguaje luzca tan extraño como Lisp. También podría ser porque si le añades ese incremento final de poder, ya no podrás pretender haber inventado un nuevo lenguaje, sino sólo un nuevo dialecto de Lisp.

Digo esto sobre todo como una broma, pero no deja de ser cierto. Si se define un lenguaje que tiene car, cdr, cons, quote, cond, atom, eq, y una notación para funciones expresadas como listas, entonces puedes construir todo el resto de Lisp a partir de eso. Esa es de hecho la característica distintiva de Lisp: era con el fin de hacer esto que McCarthy le dio la forma que tiene.
 

Donde Importan los Lenguajes


Así que supongamos que Lisp representa una especie de límite al que los lenguajes populares se acercan asintómaticamente— ¿Significa esto que en realidad debas utilizarlo para escribir software? ¿Cuánto se pierde por usar un lenguaje menos potente? ¿No es más sabio, a veces, no estar al limite mismo de la innovación? ¿Y no es la popularidad, hasta cierto punto, su propia justificación? ¿No tiene razón el jefe de pelo puntiagudo, por ejemplo, al querer utilizar un lenguaje que le permita contratar programadores fácilmente?

Hay, por supuesto, proyectos donde la elección del lenguaje de programación no importa mucho. Como regla general, mientras más exigente sea la aplicación, más ventaja se consigue usando un lenguaje potente. Pero muchos proyectos no son nada exigentes. La mayor parte de la programación consiste, probablemente, en escribir pequeños programas que se enlazan unos con otros, y para estos, se puede utilizar cualquier lenguaje que ya conozcas y que tenga buenas bibliotecas para cualquier cosa que tengas que hacer. Si sólo tienes que introducir datos de una aplicación de Windows a otra, por supuesto, usa Visual Basic.

También en Lisp se pueden escribir pequeños programas para enlazar (yo lo uso como calculadora de escritorio), pero la mayor ganancia para lenguajes como Lisp esta al otro extremo del espectro, donde necesitas escribir programas sofisticados para resolver problemas difíciles frente a una competencia feroz. Un buen ejemplo es el programa de búsquedas de tarifas aéreas que ITA Software licencia a Orbitz. Estos chicos entraron en un mercado ya dominado por dos grandes competidores atrincherados, Travelocity y Expedia, y al parecer acaban de humillarlos tecnológicamente.

El núcleo de la aplicación de ITA es un programa de 200,000 lineas de Common Lisp que busca muchas órdenes de magnitud más posibilidades que sus competidores, que al parecer siguen utilizando técnicas de programación de la era del mainframe. (Aunque ITA esta también en cierto sentido, utilizando lenguaje de programación de le era del mainframe.) Nunca he visto código de ITA, pero de acuerdo con uno de sus principales hackers utilizan una gran cantidad de macros, y no me sorprende oír eso.
 

Fuerzas Centrípetas


No estoy diciendo que no haya costo alguno por utilizar tecnologías poco comunes. El jefe de pelo puntiagudo no está completamente equivocado al preocuparse por esto. Pero debido a que no entiende los riesgos, tiende a acrecentarlos.

Se me ocurren tres problemas que pueden surgir por utilizar lenguajes menos comunes: Tus programas podrían no funcionar bien con programas escritos en otros lenguajes. Es posible que haya menos bibliotecas a tu disposición. Y podrías tener problemas para contratar programadores.

¿Qué tanto representa un problema cada uno de estos? La importancia del primero varía dependiendo de si tienes control sobre todo el sistema. Si estás escribiendo software que tiene que ejecutarse en la máquina de un usuario remoto encima de un sistema operativo cerrado lleno de errores (no menciono nombres), puede haber ventajas en escribir tu aplicación en el mismo lenguaje que el sistema operativo. Pero si controlas todo el sistema y posees el código fuente de todas las partes, como ITA presumiblemente hace, puedes usar cualquier lenguaje que desees. Si sobreviene alguna incompatibilidad, puedes repararla tu mismo.

En aplicaciones basadas en servidor puedes salirte con la tuya utilizando tecnologías más avanzadas, y creo que esta es la causa principal de lo que Jonathan Erickson llama el "renacimiento del lenguaje de programación." Esta es incluso la razón por la que escuchamos de nuevos lenguajes como Perl y Python. No estamos escuchando hablar de estos lenguajes porque la gente los use para escribir aplicaciones de Windows, sino porque los esta usando en servidores. Y conforme el software se desplace fuera del escritorio hacia los servidores (un futuro al que incluso Microsoft parece resignado), habrá cada vez menos presión para utilizar tecnologías a medio desarrollar.

En cuanto a las bibliotecas, su importancia depende también de la aplicación. En caso de problemas menos exigentes, la disponibilidad de las bibliotecas pueden pesar más que el poder intrínseco del lenguaje. ¿Dónde está el punto de equilibrio? Es difícil decirlo con exactitud, pero si lo hay, esta a cosa de nada de lo que probablemente llamarias una aplicación. Si una empresa se considera a sí misma en el negocio del software, y está escribiendo una aplicación que será uno de sus productos, entonces es probable que implique varios hackers y les tome por lo menos seis meses escribirlo. En un proyecto de ese tamaño, los lenguajes potentes probablemente comenzarán a superar la conveniencia de bibliotecas pre-existentes.

La tercera preocupación del jefe de pelo puntiagudo, la dificultad de contratar programadores, creo que es una pista falsa. Después de todo ¿cuantos hackers necesitas contratar? Seguramente a estas alturas todos sabemos que el software se desarrolla mejor en equipos menores de diez personas. Y a esa escala no deberías tener problemas contratando hackers para cualquier lenguaje del que alguien haya escuchado hablar jamás. Si no puedes encontrar diez hackers Lisp, entonces tu empresa esta probablemente ubicada en la ciudad equivocada para desarrollar software.

De hecho, elegir un lenguaje más potente probablemente disminuye el tamaño del equipo que necesitas, porque (a) si se utiliza un lenguaje más potente no serán necesarios tantos hackers, y (b) es probable que los hackers que trabajan en lenguajes más avanzados sean más inteligentes.

No estoy diciendo que no tendrás mucha presión para usar lo que se percibe como tecnologías "estándar". En Viaweb (ahora Yahoo Store), generamos ciertas inquietudes entre los capitalistas de riesgo y posibles compradores por utilizar Lisp. Sin embargo, también los inquietamos por usar servidores genéricos de Intel en lugar de servidores de “uso rudo” de Sun, por usar una variante de un entonces oscuro código abierto de Unix llamada FreeBSD en lugar de un verdadero sistema operativo comercial como Windows NT, por ignorar un supuesto estándar del  comercio electrónico llamado SET del que ahora ya nadie se acuerda, y así sucesivamente.

No puedes dejar que los hombres de negocios tomen decisiones técnicas por ti. ¿Alarmó a algunos potenciales compradores el que utilizáramos Lisp? A algunos, un poco, pero si no hubiéramos utilizado Lisp, no habríamos sido capaces de escribir el software que les hizo querer comprarnos. Lo que les parecía una anomalía fue de hecho causa y efecto.

Si inicias una startup, no diseñes tu producto para agradar a los capitalistas de riesgo o a los posibles compradores. Diseña tu producto para complacer a los usuarios. Si ganas a los usuarios, todo lo demás seguirá. Y si no, a nadie le importará que tan confortablemente ortodoxas fueron tus elecciones de tecnología.
 

El Costo de Estar en el Promedio

 
¿Cuánto se pierde por usar un lenguaje menos potente? De hecho, hay algunos datos sobre eso.

La medición más conveniente del poder es, probablemente, el tamaño del código. La idea de los lenguajes de alto nivel es darte mayores abstracciones— ladrillos más grandes, por así decirlo, de manera que no necesites tantos para construir un muro de un tamaño determinado. Por lo que mientras más potente sea el lenguaje, más corto sera el programa (no sólo en caracteres, por supuesto, sino en distintos elementos).

¿De qué manera un lenguaje más potente te permite escribir programas más cortos? Una técnica que puedes utilizar, si el lenguaje te lo permite, es algo que se llama programación de abajo-hacia arriba. En lugar de simplemente escribir tu aplicación en el lenguaje base, construyes encima del lenguaje base un lenguaje para escribir programas como el tuyo, y luego escribes el programa en el mismo. El código combinado puede ser mucho menor que si hubieras escrito el programa completo en el lenguaje base—  de hecho, así es como funcionan la mayoría de los algoritmos de compresión. Un programa de abajo-hacia arriba debería ser más fácil de modificar también, porque en muchos casos la capa del lenguaje no tendrá que cambiar en absoluto.

El tamaño del código es importante, porque el tiempo que se tarda en escribir un programa depende en gran parte de su longitud. Si tu programa sería tres veces más largo en otro lenguaje, tomará tres veces más tiempo escribirlo— y no se puede evitar esto contratando más personas, porque más allá de un cierto tamaño las nuevas contrataciones son en realidad una perdida neta. Fred Brooks describió este fenómeno en su famoso libro The Mythical Man-Month [d], y todo lo que he visto, ha tendido a confirmar lo que dijo.

Entonces, ¿cuánto más cortos son los programas si los escribes en Lisp? Casi todas las cifras que he escuchado para Lisp contra C, por ejemplo, han sido de alrededor de 7 a 10x. Sin embargo, un reciente artículo sobre ITA en la revista New Architect decía que "una línea de Lisp puede reemplazar a 20 líneas de C," y dado que este artículo estaba lleno de citas del presidente de ITA, supongo que obtuvieron esta cifra de ITA. Si es así, entonces podemos confiar un poco en ésta; el software de ITA incluye una gran cantidad de C y C++, así como de Lisp, por lo que hablan por experiencia.

Mi conjetura es que estos múltiplos no son ni siquiera una constante. Creo que aumentan cuando enfrentas problemas más difíciles y también cuando se tiene a programadores más inteligentes. Un hacker muy bueno puede aumentar el rendimiento de las mejores herramientas.

En todo caso, como punto de referencia en la curva, si tuvieras que competir con ITA y decidieras escribir tu software en C, serían capaces de desarrollar software veinte veces más rápido que tú. Si pasas un año en una nueva característica, podrían ser capaces de duplicarla en menos de tres semanas. Mientras que si ellos pasan sólo tres meses desarrollando algo nuevo, tomaría cinco años antes de que tú también la tuvieras.

¿Y sabes qué? Eso en el mejor de los casos. Cuando se habla de proporciones de tamaño de código, estás asumiendo implícitamente que puedes en realidad escribir el programa en el lenguaje más débil. Pero de hecho, hay límites en lo que los programadores pueden hacer. Si estás tratando de resolver un problema difícil con un lenguaje de muy bajo nivel, se llega a un punto donde simplemente hay demasiadas cosas para tener en la cabeza a la vez.

Así que cuando digo que le tomaría cinco años al competidor imaginario de ITA duplicar algo que ITA podría escribir en Lisp en tres meses, quiero decir cinco años, si nada va mal. De hecho, por cómo funcionan las cosas en la mayoría de las empresas, cualquier proyecto de desarrollo que tome cinco años es probable que nunca se termine.

Admito que este es un caso extremo. Los hackers de ITA parecen ser excepcionalmente inteligentes, y C es un lenguaje de muy bajo nivel. Pero en un mercado competitivo, incluso una diferencia de dos o tres a uno sería suficiente para garantizar que siempre estarías detrás.
 

Una Receta


Este es el tipo de posibilidad que el jefe de pelo puntiagudo ni siquiera desea valorar. Casi ninguno de ellos. Porque, ya lo sabes, cuando se llega a esto, al jefe de pelo puntiagudo no le importa si a su empresa le patean el trasero, siempre y cuando no se pueda comprobar que es su culpa. El plan más seguro para él personalmente es permanecer cerca del centro de la manada.

Dentro de las grandes organizaciones, la frase utilizada para describir este enfoque es "la mejor práctica en la industria". Su propósito es proteger al jefe de pelo puntiagudo de la responsabilidad: si elige algo que es "la mejor práctica en la industria", y la empresa pierde, no puede ser culpado. Él no escogió, la industria lo hizo.

Creo que este término fue utilizado originalmente para describir métodos de contabilidad, y otros por el estilo. Lo que significa, aproximadamente, es: no hagas nada raro. Y en la contabilidad esa es probablemente una buena idea. Los términos "de vanguardia" y "contabilidad" no suenan bien juntos. Sin embargo, al importar este criterio a decisiones sobre tecnología, comienzas a obtener respuestas incorrectas.

La tecnología a menudo debe ser de vanguardia. En lenguajes de programación, como Erann Gat ha señalado, lo que "las mejores prácticas de la industria" en realidad te consiguen no es lo mejor, sino simplemente la media. Cuando una decisión hace que desarrolles software a una fracción de la tasa de los competidores más agresivos, "la mejor práctica" es un término equivocado.

Así que aquí tenemos dos fragmentos de información que creo son muy valiosos. De hecho, lo sé por mi propia experiencia. Número 1: Los lenguajes varían en poder. Número 2: La mayoría de los gerentes ignoran esto deliberadamente. Juntos, estos dos hechos son, literalmente, una receta para hacer dinero. ITA es un ejemplo de esta receta en acción. Si quieres ganar en un negocio de software, aborda el problema más difícil que puedas encontrar, utiliza el lenguaje más potente que puedas conseguir, y espera a que el jefe de pelo puntiagudo de tu competencia vuelva a la media.





Apéndice: Potencia

 
A manera de ilustración de lo que quiero decir sobre el poder relativo de los lenguajes de programación, consideremos el siguiente problema. Queremos escribir una función que genere acumuladores— una función que toma un número n, y devuelve una función que toma otro número i y devuelve n incrementado por i.

(Quiero decir incrementado por, no más. Un acumulador tiene que acumular.)

En Common Lisp esto sería
 
(defun foo (n)
  (lambda (i) (incf n i)))

Y en Perl 5,
 
sub foo {  
  my ($n) = @_;
  sub {$n += shift}
}

el cual tiene más elementos que la versión de Lisp por que en Perl tienes que extraer los parámetros manualmente.

En Smalltalk el código es ligeramente más largo que en Lisp
 
foo: n                              
  |s|                      
  s := n.                          
  ^[:i| s := s+i. ]

porque aunque en general las variantes lexicas funcionan, no puedes hacer una asignación a un parámetro, así que tienes que crear una variable s.

En Javascript el ejemplo es, otra vez, ligeramente más largo, por que Javascript retiene la distinción entre declaraciones y expresiones, asi que necesitas declaraciones return explícitas para devolver valores:
 
function foo(n) {
  return function (i) {
           return n += i } }

(Para ser justos, Perl también retiene esta distinción, pero lidia con ella a la manera típica de Perl, dejándote omitir returnS.)
 
Si tratas de traducir el código Lisp/Perl/Smalltalk /Javascript a Python te encuentras con algunas limitaciones. Debido a que Python no es totalmente compatible con las variables léxicas, tienes que crear una estructura de datos para mantener el valor de n. Y aunque Python sí tiene una función para tipo de datos, no existe una representación literal de una (a menos que el cuerpo sea sólo una expresión simple) por lo que necesitas crear una función con nombre para return. Esto es con lo que terminas:
 
def foo(n):
  s = [n]
  def bar(i):
    s[0] += i
    return s[0]
  return bar

Los usuarios de Python podrían legítimamente preguntar por que no pueden simplemente escribir
 
def foo(n):
  return lambda i: return n += i

o incluso
 
def foo(n):
  lambda i: n += i

y mi opinión es que es probablemente podrán, algún día. (Pero si no quieren esperar a que Python evolucione lo que le falta del camino a Lisp, siempre podrían simplemente...)

En lenguajes OO, puedes, hasta cierto punto, simular una clausura (una función que referencia a variables definidas en ámbitos cerrados) mediante la definición de una clase con un método y un campo para reemplazar cada variable a partir de un ámbito cerrado. Esto hace que el programador haga el tipo de análisis de código que se llevaría a cabo por el compilador en un lenguaje con pleno soporte para ámbito léxico, y no funcionará si más de una función referencia a la misma variable, pero es suficiente en casos sencillos como este.

Los expertos de Python parecen estar de acuerdo que esta es la mejor manera de resolver el problema en Python, escribiendo ya sea
 
def foo(n):
  class acc:
    def __init__(self, s):
        self.s = s
    def inc(self, i):
        self.s += i
        return self.s
  return acc(n).inc

o
 
class foo:
  def __init__(self, n):
      self.n = n
  def __call__(self, i):
      self.n += i
      return self.n


Incluyo estos porque no me gustaría que los partidarios de Python digan que estoy describiendo engañosamente el lenguaje, pero ambos me parecen más complejos que la primera versión. Estás haciendo lo mismo, creando un espacio independiente para ubicar el acumulador; es sólo un campo en un objeto en lugar de la cabeza de una lista. Y el uso de estos nombres especiales de campos reservados, en particular __call__ , parece un tanto como algo improvisado.

En la rivalidad entre Perl y Python, la pretensión de los hackers de Python parece ser que Python es una alternativa más elegante que Perl, pero lo que este caso demuestra es que el poder es la elegancia final: el programa de Perl es más sencillo (tiene menos elementos), aun si la sintaxis es un poco fea.

¿Qué tal otros lenguajes? En los otros lenguajes mencionados en este ensayo —Fortran, C, C++, Java y Visual Basic— no está claro si en realidad puede resolverse este problema. Ken Anderson dice que el codigo siguiente es lo más cercano que se puede llegar en Java:

 
public interface Inttoint {
  public int call(int i);
}
 

public static Inttoint foo(final int n) {
  return new Inttoint() {
    int s = n;
    public int call(int i) {
    s = s + i;
    return s;
    }};
}


Esto no está a la altura de las especificaciones, ya que sólo funciona para los números enteros. Después de un intercambio de correos electrónicos con muchos hackers Java, yo diría que escribir una versión polimórfica adecuada que se comporte como los ejemplos anteriores se encuentra entre endemoniadamente complicada e imposible. Si alguien quiere escribir una tendría mucha curiosidad en verla, pero yo personalmente he agotado mi tiempo.

Por supuesto, no es literalmente cierto que no se pueda resolver este problema en otros lenguajes. El hecho de que todos estos lenguajes son Turing-equivalentes significa que, en sentido estricto, puedes escribir cualquier programa en cualquiera de ellos. Entonces, ¿cómo lo harías? En el caso límite, escribiendo un intérprete de Lisp en el lenguaje menos potente.

Eso suena como una broma, pero sucede tan a menudo a diversos grados en proyectos de programación de gran tamaño que hasta hay un nombre para el fenómeno, La Regla Décima de Greenspun:
 
Cualquier programa de C o Fortran lo suficientemente complicado contiene una implementación ad hoc especificada de manera informal, lenta, llena de errores, de la mitad de Common Lisp.
 
Si intentas resolver un problema difícil, la cuestión no es si utilizaras un lenguaje lo suficientemente potente, sino si (a) usaras un lenguaje potente, (b) escribiras un intérprete de facto para uno, o (c) tú mismo te convertiras en un compilador humano para el mismo. Vemos que esto ya empieza a ocurrir en el ejemplo de Python, donde estamos, en efecto, simulando el código que un compilador generaría para implementar una variable léxica.

Esta práctica no sólo es común, sino institucionalizada. Por ejemplo, en el mundo OO escuchas mucho sobre los "patrones". Me pregunto si estos patrones no son a veces la evidencia del caso (c), el compilador humano en acción. Cuando veo patrones en mis programas, considero que es una señal de problemas. La forma de un programa debe reflejar sólo el problema que necesita resolver. Cualquier otra regularidad en el código es una señal, al menos para mí, que no estoy usando abstracciones lo suficientemente potentes— a menudo, que estoy generando a mano las expansiones de algunas macros que necesito escribir.

 

Notas

 
  • La CPU IBM 704 era del tamaño de un refrigerador, pero mucho más pesada. La CPU pesaba 1429 kilogramos, y los 4K de RAM se encontraban en una caja separada que pesaba otros 1814 kilogramos. El Sub-Zero 690, uno de los refrigeradores caseros más grandes, pesa 298 kilogramos.
  • Steve Russell escribió también el primer juego (digital) de computadora, Spacewar, en 1962.
  • Si quieres engañar a un jefe de pelo puntiagudo para que te permita escribir programas en Lisp, podrías intentar decirle que es XML.
  • Este es el generador de acumuladores en otros dialectos de Lisp:
      Scheme: (define (foo n)
                (lambda (i) (set! n (+ n i)) n))
      Goo:    (df foo (n) (op incf n _)))
      Arc:    (def foo (n) [++ n _])
 
  • La triste historia de Erann Gat sobre las "mejores prácticas de la industria" en el JPL me inspiró para abordar esta frase generalmente mal aplicada.
  • Gracias a las muchas personas que respondieron a mis preguntas acerca de varios lenguajes y/o por leer los borradores de éste ensayo, incluyendo a Ken Anderson, Trevor Blackwell, Erann Gat, Dan Giffin, Sarah Harlin, Jeremy Hylton, Robert Morris, Peter Norvig, Guy Steele, y Anton van Straaten. No tienen ninguna culpa de cualquiera de las opiniones expresadas.
 
 

Relacionado:

 
Muchas personas han respondido a esta charla, por lo que he creado una página adicional para tratar las cuestiones que ha generado: Re: La Venganza de los Nerds.

También desencadenó una discusión amplia y a menudo útil en la lista de correo LL1. Véase en particular el correo por Anton van Straaten sobre compresión semántica.

Algunos de los correos en LL1 me llevaron a tratar de profundizar en el tema del poder del lenguaje en Concisión es Poder.

Un conjunto más amplio de implementaciones canónicas de referencia del generador del acumulador estan reunidas en su propia página.





Traducido de Revenge of the Nerds por Paul Graham. Traducción: Armando Alvarez.


 

Notas del Traductor


[a] James Gosling (nacido el 19 de mayo de 1956 cerca de Calgary, Alberta, Canadá) es un famoso científico de la computación conocido como el padre del lenguaje de programación Java.

Se graduó en Ciencias en la Universidad de Calgary en 1977 y realizó el doctorado en la famosa Universidad de Carnegie Mellon en 1983.

Gosling es reconocido como el creador del lenguaje de programación Java. Realizó el diseño original y la implementación del compilador original y la máquina virtual Java, por lo que fue elegido miembro de la Academia Nacional de Ingeniería de Estados Unidos (NAE). Además, Gosling ha contribuido con otros proyectos de software como NeWS y Gosling Emacs. [Fuente: Wikipedia. Ver.]

[b] John McCarthy (n. 4 de septiembre de 1927, Boston, Massachusetts), también conocido como Tío John McCarthy, es un prominente informático que recibió el Premio Turing en 1971 por sus importantes contribuciones en el campo de la Inteligencia Artificial. De hecho, fue el responsable de introducir el término “inteligencia artificial”, concepto que acuñó en la Conferencia de Dartmouth en 1956.

McCarthy inventó el lenguaje de programación Lisp y publicó su diseño en Comunicaciones del ACM en 1960.

En 1961, fue el primero en sugerir públicamente (en un discurso dado para celebrar el centenario del MIT) que la tecnología de tiempo compartido de las computadoras podría conducir a un futuro en el que el poder del cómputo e incluso aplicaciones específicas podrían ser vendidas como un servicio (como el agua o la electricidad). [Fuente Wikipedia. Ver].

[c] El ordenamiento rápido (quicksort en inglés) es un algoritmo creado por el científico británico en computación C. A. R. Hoare basado en la técnica de divide y vencerás, que permite, en promedio, ordenar n elementos en un tiempo proporcional a n log n. [Fuente: Wikipedia. Ver].

[d] El Mítico Hombre-Mes: Ensayos de ingeniería de Software (en inglés The Mythical Man-Month: Essays on Software Engineering) es un libro de administración de proyectos de Software de Fred Brooks, cuyo tema central establece que "agregar recursos humanos a un proyecto retrasado lo hace demorarse aún más". Esta idea es conocida como la "Ley de Brooks".

El trabajo fue publicado en 1975, y republicado como una versión de aniversario en 1995 (ISBN 0-201-83595-9) junto al ensayo "Sin balas de plata" y comentarios por el autor.

Las observaciones de Brooks están basadas en sus experiencias en IBM mientras administraba el desarrollo de OS/360. Para acelerar el desarrollo, se trató infructuosamente de agregar más trabajadores al proyecto que ya estaba retrasado. También apostó que escribir un compilador en ALGOL requería "6 meses de mano de obra" adicional. La tendencia de quienes administran de repetir estos errores llevaron a Brooks a la conclusión de "la biblia de la ingeniería de software" porque "todos la leen pero nadie la practica". [Fuente: Wikipedia. Ver]