LA VENGANZA DE LOS NERDS
OriginalMayo de 2002
"Estábamos buscando a los programadores de C++. Logramos arrastrar a muchos de ellos hasta la mitad del camino hacia Lisp".
- Guy Steele, coautor de la especificación de Java
En el sector 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? Creo que la mayoría de las personas del mundo de la tecnología no sólo reconocen a este personaje de dibujos animados, sino que conocen a la persona real de su empresa en la que se basa.
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 sobre tecnología, y (b) tiene opiniones muy firmes sobre ella.
Supongamos, por ejemplo, que necesitas escribir un programa. El jefe de pelo puntiagudo no tiene idea de cómo tiene que funcionar ese programa y no puede distinguir un lenguaje de programación de otro, pero sabe en qué lenguaje deberías escribirlo. Exactamente. Cree que deberías escribirlo en Java.
¿Por qué piensa esto? Echemos un vistazo al cerebro del jefe de pelo puntiagudo. Lo que está pensando es algo así: Java es un estándar. Sé que debe serlo, porque leo sobre ello en la prensa todo el tiempo. Como es un estándar, no tendré problemas por usarlo. Y eso también significa que siempre habrá muchos programadores Java, así que si los programadores que trabajan para mí ahora dejan el trabajo, como misteriosamente siempre hacen los programadores que trabajan para mí, puedo reemplazarlos fácilmente.
Bueno, esto no suena tan descabellado, pero todo se basa en una suposición tácita, y esa suposición resulta ser falsa. El jefe de pelo puntiagudo cree que todos los lenguajes de programación son prácticamente equivalentes. Si eso fuera cierto, estaría en lo cierto. Si todos los lenguajes son equivalentes, claro, usen el lenguaje que usen los demás.
Pero no todos los lenguajes son equivalentes, y creo que puedo demostrártelo sin siquiera entrar en las diferencias entre ellos. Si en 1992 le hubieras preguntado al jefe de pelo puntiagudo en qué lenguaje debería escribirse el software, te habría contestado con la misma facilidad con la que lo hace hoy: el software debería escribirse en C++. Pero si todos los lenguajes son equivalentes, ¿por qué debería cambiar la opinión del jefe de pelo puntiagudo? De hecho, ¿por qué los desarrolladores de Java se habrían molestado siquiera en crear un nuevo lenguaje?
Presumiblemente, si creas un nuevo lenguaje es porque piensas que es mejor de alguna manera que lo que la gente ya tenía. Y de hecho, Gosling deja claro en el primer libro blanco 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 el rastro a través del cerebro del jefe de pelo puntiagudo hasta Java y luego retrocedes a través de la historia de Java hasta sus orígenes, terminas sosteniendo una idea que contradice la suposición con la que comenzaste.
Entonces, ¿quién tiene razón? ¿James Gosling o el jefe de pelo puntiagudo? No es de sorprender que Gosling tenga razón. Algunos lenguajes son mejores que otros para ciertos problemas. Y, como saben, eso plantea algunas preguntas interesantes. Java fue diseñado para ser mejor que C++ para ciertos problemas. ¿Qué problemas? ¿Cuándo es mejor Java y cuándo C++? ¿Hay situaciones en las que otros lenguajes son mejores que cualquiera de ellos?
En cuanto uno empieza a plantearse esta cuestión, se abre una auténtica caja de Pandora. Si el jefe de pelo puntiagudo tuviera que pensar en el problema en toda su complejidad, le explotaría el cerebro. Mientras considere que todos los lenguajes son equivalentes, todo lo que tiene que hacer es elegir el que parezca tener más impulso, y como eso es más una cuestión de moda que de tecnología, es probable que incluso él pueda obtener la respuesta correcta. Pero si los lenguajes varían, de repente tiene que resolver dos ecuaciones simultáneas, tratando de encontrar un equilibrio óptimo entre dos cosas de las que no sabe nada: la idoneidad relativa de los veinte lenguajes más importantes para el problema que necesita resolver y las probabilidades de encontrar programadores, bibliotecas, etc. para cada uno. Si eso es lo que hay 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 sencilla. Y creo que esa es la razón principal por la que la idea está tan extendida. Es una idea cómoda .
Sabemos que Java debe ser bastante bueno, porque es el nuevo lenguaje de programación de moda. ¿O no? Si miras el mundo de los lenguajes de programación desde la distancia, parece que Java es lo último. (Desde la distancia suficiente, todo lo que puedes ver es el gran cartel publicitario resplandeciente pagado por Sun). Pero si miras este mundo de cerca, descubres que hay grados de genialidad. Dentro de la subcultura hacker, hay otro lenguaje llamado Perl que se considera mucho más genial que Java. Slashdot, por ejemplo, está generado por Perl. No creo que encuentres a esos tipos usando Java Server Pages. Pero hay otro lenguaje más nuevo, llamado Python, cuyos usuarios tienden a mirar por encima del hombro a Perl, y hay más esperando su turno.
Si observas estos lenguajes en orden (Java, Perl y Python), observarás un patrón interesante. Al menos, lo observarás si eres un hacker de Lisp. Cada uno de ellos se parece cada vez más a Lisp. Python copia incluso características que muchos hackers de Lisp consideran errores. Podrías traducir programas sencillos de Lisp a Python línea por línea. Estamos en 2002 y los lenguajes de programación casi han alcanzado a 1958.
Poniéndose al día con las matemáticas
Lo que quiero decir es que Lisp fue descubierto por primera vez por John McCarthy en 1958, y los lenguajes de programación populares recién ahora están alcanzando las ideas que él desarrolló entonces.
Ahora bien, ¿cómo podría ser cierto eso? ¿Acaso la tecnología informática no es algo que cambia muy rápidamente? Quiero decir, en 1958, las computadoras eran gigantes del tamaño de un refrigerador con la capacidad de procesamiento de un reloj de pulsera. ¿Cómo podría una tecnología tan antigua ser relevante, y mucho menos superior a los últimos avances?
Te lo voy a explicar. Es porque Lisp no fue diseñado realmente para ser un lenguaje de programación, al menos no en el sentido que le damos hoy. Lo que entendemos por lenguaje de programación es algo que usamos para decirle a una computadora qué hacer. McCarthy finalmente tuvo la intención de desarrollar un lenguaje de programación en este sentido, pero el Lisp con el que finalmente terminamos se basó en algo separado que él hizo como un ejercicio teórico , un esfuerzo por definir una alternativa más conveniente a la Máquina de Turing. Como McCarthy dijo más tarde:
Otra forma de demostrar que Lisp era más ordenado que las máquinas de Turing era escribir una función Lisp universal y demostrar que era más breve y comprensible que la descripción de una máquina de Turing universal. Se trataba de la función Lisp eval ..., que calcula el valor de una expresión Lisp... Escribir eval requería inventar una notación que representara las funciones Lisp como datos Lisp, y dicha notación se ideó para los fines del artículo sin pensar que se usaría para expresar programas Lisp en la práctica.
Lo que ocurrió después fue que, a finales de 1958, Steve Russell, uno de los estudiantes de posgrado de McCarthy, miró esta definición de eval y se dio cuenta de que si la traducía al lenguaje de máquina, el resultado sería un intérprete Lisp.
En su momento, esto fue una gran sorpresa. Esto es lo que McCarthy dijo al respecto más tarde en una entrevista:
Steve Russell me dijo: "Mira, ¿por qué no programo este eval ...?", y yo le dije: "Jo, jo, estás confundiendo la teoría con la práctica, este eval está pensado para leer, no para computar". Pero él siguió adelante y lo hizo. Es decir, compiló el eval de mi artículo en código de máquina [IBM] 704, corrigió errores y luego lo promocionó como un intérprete de Lisp, lo que ciertamente era. Así que en ese momento Lisp tenía esencialmente la forma que tiene hoy...
De repente, creo que en cuestión de semanas, McCarthy descubrió que su ejercicio teórico se había transformado en un lenguaje de programación real, más potente de lo que pretendía.
Así que la explicación breve de por qué este lenguaje de los años 50 no está obsoleto es que no era tecnología sino matemáticas, y las matemáticas no se vuelven obsoletas. Lo correcto con lo que comparar Lisp no es el hardware de los años 50, sino, por ejemplo, el algoritmo Quicksort, que se descubrió en 1960 y sigue siendo el algoritmo de ordenación de propósito general más rápido.
Existe otro lenguaje que aún sobrevive desde los años 50, Fortran, y representa el enfoque opuesto al diseño de lenguajes. Lisp era una pieza de teoría que inesperadamente se convirtió en un lenguaje de programación. Fortran fue desarrollado intencionalmente como un lenguaje de programación, pero lo que hoy consideraríamos un lenguaje de muy bajo nivel.
Fortran I , el lenguaje que se desarrolló en 1956, era muy diferente del Fortran actual. Fortran I era básicamente un lenguaje ensamblador con matemáticas. En algunos aspectos era menos potente que los lenguajes ensambladores más recientes; no había subrutinas, por ejemplo, solo ramas. El Fortran actual se parece más a Lisp que a Fortran I.
Lisp y Fortran eran los troncos de dos árboles evolutivos separados, uno con raíces en las matemáticas y otro con raíces en la arquitectura de las máquinas. Estos dos árboles han ido convergiendo desde entonces. Lisp comenzó siendo potente y durante los siguientes veinte años se volvió rápido. Los llamados lenguajes convencionales comenzaron rápido y durante los siguientes cuarenta años gradualmente se volvieron más poderosos, hasta que ahora los más avanzados de ellos están bastante cerca de Lisp. Cerca, pero todavía les faltan algunas cosas...
¿Qué hizo diferente a Lisp?
Cuando se desarrolló por primera vez, Lisp incorporaba nueve ideas nuevas. Algunas de ellas ahora las damos por sentadas, otras solo se ven en lenguajes más avanzados y dos siguen siendo exclusivas de Lisp. Las nueve ideas son, en orden de adopción por parte del público en general,
Condicionales. Un condicional es una construcción if-then-else. Ahora las damos por sentado, pero Fortran I no las tenía. Solo tenía un condicional goto basado estrechamente en la instrucción de máquina subyacente.
Un tipo de función. En Lisp, las funciones son un tipo de datos, al igual que los números enteros o las cadenas. Tienen una representación literal, se pueden almacenar en variables, se pueden pasar como argumentos, etc.
Recursión. Lisp fue el primer lenguaje de programación que lo admitió.
Tipado dinámico. En Lisp, todas las variables son efectivamente punteros. Los valores son los que tienen tipos, no las variables, y asignar o vincular variables significa copiar punteros, no aquello a lo que apuntan.
Recogida de basura.
Programas compuestos de expresiones. Los programas Lisp son árboles de expresiones, cada una de las cuales devuelve un valor. Esto contrasta con Fortran y la mayoría de los lenguajes posteriores, que distinguen entre expresiones y sentencias.
Era natural tener esta distinción en Fortran I porque no se podían anidar instrucciones. Y, por lo tanto, si bien se necesitaban expresiones para que las matemáticas funcionaran, no tenía sentido hacer que cualquier otra cosa devolviera un valor, porque no podía haber nada esperando por ello.
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 enunciados estaba arraigada. Se extendió de Fortran a Algol y luego a sus dos descendientes.
Un tipo de símbolo. Los símbolos son, en efecto, punteros a cadenas almacenadas en una tabla hash. Por lo tanto, puede comprobar la igualdad comparando un puntero, en lugar de comparar cada carácter.
Una notación para código que utiliza árboles de símbolos y constantes.
Todo el lenguaje está ahí todo el tiempo. No hay una distinción real entre tiempo de lectura, tiempo de compilación y tiempo de ejecución. Puedes compilar o ejecutar código mientras lees, leer o ejecutar código mientras compilas y leer o compilar código en tiempo de ejecución.
La ejecución de código en tiempo de lectura permite a los usuarios reprogramar la sintaxis de Lisp; la ejecución de código en tiempo de compilación es la base de las macros; la compilación 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 usando expresiones s, una idea recientemente reinventada como XML.
Cuando apareció Lisp por primera vez, estas ideas estaban muy alejadas de la práctica de programación habitual, que estaba dictada en gran medida por el hardware disponible a finales de los años 50. Con el tiempo, el lenguaje predeterminado, encarnado en una sucesión de lenguajes populares, ha ido evolucionando gradualmente hacia Lisp. Las ideas 1 a 5 están ahora muy extendidas. La número 6 está empezando a aparecer en la corriente principal. Python tiene una forma de 7, aunque no parece haber ninguna sintaxis para ello.
En cuanto al número 8, puede que sea el más interesante de todos. Las ideas 8 y 9 sólo llegaron a formar parte de Lisp por accidente, porque Steve Russell implementó algo que McCarthy nunca había tenido intención de implementar. 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 parece extraño no tanto porque tenga una sintaxis extraña, sino porque no tiene sintaxis; los programas se expresan directamente en los árboles de análisis que se construyen entre bastidores cuando se analizan otros lenguajes, y estos árboles están hechos de listas, que son estructuras de datos de Lisp.
La expresión del lenguaje en sus propias estructuras de datos resulta ser una característica muy potente. Las ideas 8 y 9 juntas significan que se pueden escribir programas que escriban programas. Puede parecer una idea extraña, pero es algo cotidiano en Lisp. La forma más común de hacerlo es con algo llamado macro.
El término "macro" no significa en Lisp lo mismo que en otros lenguajes. Una macro de Lisp puede ser cualquier cosa, desde una abreviatura hasta un compilador para un nuevo lenguaje. Si quieres entender realmente Lisp, o simplemente ampliar tus horizontes de programación, te recomiendo que aprendas más sobre macros.
Las macros (en el sentido de Lisp) siguen siendo, hasta donde yo sé, exclusivas de Lisp. Esto se debe en parte a que para tener macros probablemente tengas que hacer que tu lenguaje parezca tan extraño como Lisp. También puede deberse a que si agregas ese incremento final de potencia, ya no puedes afirmar que has inventado un nuevo lenguaje, sino solo un nuevo dialecto de Lisp.
Menciono esto principalmente como una broma, pero es muy cierto. Si defines 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 él. De hecho, esa es la cualidad que define a Lisp: para lograrlo, McCarthy le dio a Lisp la forma que tiene.
Donde los idiomas importan
Supongamos entonces que Lisp representa una especie de límite al que los lenguajes convencionales se están acercando asintóticamente: ¿significa eso que realmente deberías usarlo para escribir software? ¿Cuánto pierdes al usar un lenguaje menos potente? ¿No es más sensato, a veces, no estar en el borde 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 usar un lenguaje para el que puede contratar fácilmente programadores?
Por supuesto, hay proyectos en los que la elección del lenguaje de programación no importa demasiado. Por regla general, cuanto más exigente sea la aplicación, más ventajas obtendrás al utilizar un lenguaje potente. Pero muchos proyectos no son exigentes en absoluto. La mayor parte de la programación probablemente consista en escribir pequeños programas de unión, y para estos programas puedes utilizar cualquier lenguaje con el que ya estés familiarizado y que tenga buenas bibliotecas para lo que necesites hacer. Si solo necesitas pasar datos de una aplicación de Windows a otra, seguro que utilizas Visual Basic.
También se pueden escribir pequeños programas en Lisp (yo lo uso como calculadora de escritorio), pero la mayor ventaja de lenguajes como Lisp está en el otro extremo del espectro, donde es necesario escribir programas sofisticados para resolver problemas difíciles frente a una competencia feroz. Un buen ejemplo es el programa de búsqueda de tarifas aéreas que ITA Software licencia a Orbitz. Estos tipos entraron en un mercado ya dominado por dos grandes competidores arraigados, Travelocity y Expedia, y parece que acaban de humillarlos tecnológicamente.
El núcleo de la aplicación de ITA es un programa Common Lisp de 200.000 líneas que busca muchos órdenes de magnitud más posibilidades que sus competidores, quienes aparentemente siguen utilizando técnicas de programación de la era de los mainframes (aunque ITA también utiliza, en cierto sentido, un lenguaje de programación de la era de los mainframes). Nunca he visto ningún código de ITA, pero según uno de sus mejores hackers, utilizan muchas macros, y no me sorprende oírlo.
Fuerzas centrípetas
No digo que no haya costos por usar tecnologías poco comunes. El jefe de pelo puntiagudo no se equivoca del todo al preocuparse por esto, pero como no comprende los riesgos, tiende a magnificarlos.
Se me ocurren tres problemas que podrían surgir al utilizar lenguajes menos comunes. Es posible que tus programas no funcionen bien con programas escritos en otros lenguajes. Es posible que tengas menos bibliotecas a tu disposición. Y es posible que tengas problemas para contratar programadores.
¿Qué grado de problema supone cada uno de estos? La importancia del primero varía en función de si se tiene control sobre todo el sistema. Si se está escribiendo un software que tiene que ejecutarse en la máquina de un usuario remoto sobre un sistema operativo cerrado y lleno de errores (no menciono nombres), puede haber ventajas en escribir la aplicación en el mismo lenguaje que el SO. Pero si se controla todo el sistema y se tiene el código fuente de todas las partes, como presumiblemente hace ITA, se pueden utilizar los lenguajes que se deseen. Si surge alguna incompatibilidad, se puede solucionar por uno mismo.
En las aplicaciones basadas en servidores se puede salirse con la suya utilizando las tecnologías más avanzadas, y creo que ésta es la causa principal de lo que Jonathan Erickson llama el " renacimiento de los lenguajes de programación ". Ésta es la razón por la que incluso oímos hablar de nuevos lenguajes como Perl y Python. No oímos hablar de estos lenguajes porque la gente los esté utilizando para escribir aplicaciones de Windows, sino porque los están utilizando en servidores. Y a medida que el software pase del escritorio a los servidores (un futuro al que incluso Microsoft parece resignarse), habrá cada vez menos presión para utilizar tecnologías intermedias.
En cuanto a las bibliotecas, su importancia también depende de la aplicación. Para problemas menos exigentes, la disponibilidad de bibliotecas puede superar el poder intrínseco del lenguaje. ¿Dónde está el punto de equilibrio? Es difícil decirlo con exactitud, pero donde sea, no llega a ser lo que se podría llamar una aplicación. Si una empresa se considera parte del negocio del software y está escribiendo una aplicación que será uno de sus productos, entonces probablemente involucrará a varios piratas informáticos y tardará al menos seis meses en escribirla. En un proyecto de ese tamaño, los lenguajes potentes probablemente comiencen a superar la conveniencia de las bibliotecas preexistentes.
La tercera preocupación del jefe de pelo puntiagudo, la dificultad de contratar programadores, creo que es una pista falsa. ¿Cuántos hackers necesitas contratar, después de todo? Seguramente a estas alturas todos sabemos que el software se desarrolla mejor en equipos de menos de diez personas. Y no deberías tener problemas para contratar hackers a esa escala para cualquier lenguaje del que hayas oído hablar. Si no puedes encontrar diez hackers de Lisp, entonces tu empresa probablemente esté ubicada en la ciudad equivocada para desarrollar software.
De hecho, elegir un lenguaje más potente probablemente reduce el tamaño del equipo que necesitas, porque (a) si usas un lenguaje más potente probablemente no necesitarás tantos hackers, y (b) los hackers que trabajan en lenguajes más avanzados probablemente sean más inteligentes.
No digo que no habrá mucha presión para que utilicemos tecnologías que se perciben como "estándar". En Viaweb (ahora Yahoo Store), sorprendimos a algunos inversores de capital riesgo y potenciales compradores por utilizar Lisp. Pero también sorprendimos a otros por utilizar equipos Intel genéricos como servidores en lugar de servidores "de potencia industrial" como los Sun, por utilizar una variante de Unix de código abierto entonces desconocida llamada FreeBSD en lugar de un sistema operativo comercial real como Windows NT, por ignorar un supuesto estándar de comercio electrónico llamado SET que ahora nadie recuerda, etc.
No se puede dejar que los ejecutivos tomen decisiones técnicas por uno. ¿Alarmaron a algunos compradores potenciales el hecho de que usáramos Lisp? A algunos, un poco, pero si no hubiéramos usado Lisp, no habríamos podido escribir el software que hizo que quisieran comprarnos. Lo que para ellos parecía una anomalía era, de hecho, una cuestión de causa y efecto.
Si crea una empresa emergente, no diseñe su producto para complacer a los inversores de capital riesgo o a los posibles compradores. Diseñe su producto para complacer a los usuarios. Si gana a los usuarios, todo lo demás vendrá por añadidura. Y si no lo hace, a nadie le importará lo cómodas y ortodoxas que hayan sido sus elecciones tecnológicas.
El costo de ser promedio
¿Cuánto se pierde al utilizar un lenguaje menos potente? De hecho, hay algunos datos al respecto.
La medida más conveniente de potencia es probablemente el tamaño del código . El objetivo de los lenguajes de alto nivel es ofrecer abstracciones mayores (bloques más grandes, por así decirlo) para que no se necesiten tantos para construir un muro de un tamaño determinado. Por lo tanto, cuanto más potente sea el lenguaje, más corto será el programa (no solo en caracteres, por supuesto, sino en elementos distintos).
¿Cómo un lenguaje más potente le permite escribir programas más cortos? Una técnica que puede utilizar, si el lenguaje lo permite, es algo llamado programación ascendente . En lugar de simplemente escribir su aplicación en el lenguaje base, construye sobre el lenguaje base un lenguaje para escribir programas como el suyo y luego escribe su programa en él. El código combinado puede ser mucho más corto que si hubiera escrito todo su programa en el lenguaje base; de hecho, así es como funcionan la mayoría de los algoritmos de compresión. Un programa ascendente también debería ser más fácil de modificar, porque en muchos casos la capa de lenguaje no tendrá que cambiar en absoluto.
El tamaño del código es importante, porque el tiempo que lleva escribir un programa depende principalmente de su longitud. Si tu programa fuera tres veces más largo en otro lenguaje, tardarás tres veces más en escribirlo, y no puedes evitarlo contratando a más gente, porque más allá de cierto tamaño, las nuevas contrataciones son en realidad una pérdida neta. Fred Brooks describió este fenómeno en su famoso libro The Mythical Man-Month, y todo lo que he visto tiende a confirmar lo que dijo.
¿Cuánto más cortos son entonces tus programas si los escribes en Lisp? La mayoría de las cifras que he oído sobre Lisp en comparación con C, por ejemplo, han sido de alrededor de 7 a 10 veces. Pero un artículo reciente sobre ITA en la revista New Architect decía que "una línea de Lisp puede reemplazar 20 líneas de C", y como este artículo estaba lleno de citas del presidente de ITA, supongo que obtuvieron esta cifra de ITA. Si es así, entonces podemos tener cierta fe en ello; el software de ITA incluye mucho C y C++ además de Lisp, por lo que hablan por experiencia.
Supongo que estos múltiplos ni siquiera son constantes. Creo que aumentan cuando te enfrentas a problemas más difíciles y también cuando tienes programadores más inteligentes. Un hacker realmente bueno puede sacar más provecho de mejores herramientas.
En cualquier caso, como punto de referencia, si compitieras con ITA y decidieras escribir tu software en C, ellos podrían desarrollar software veinte veces más rápido que tú. Si dedicabas un año a una nueva característica, ellos podrían duplicarla en menos de tres semanas. Mientras que si dedicaban solo tres meses a desarrollar algo nuevo, pasarían cinco años antes de que tú también lo tuvieras.
¿Y sabes qué? Ese es el mejor escenario posible. Cuando hablas de proporciones de código-tamaño, estás asumiendo implícitamente que puedes escribir el programa en el lenguaje más débil. Pero, de hecho, hay límites a lo que los programadores pueden hacer. Si estás tratando de resolver un problema difícil con un lenguaje que es de muy bajo nivel, llegas a un punto en el que hay demasiadas cosas para mantener en tu cabeza a la vez.
Así que cuando digo que el competidor imaginario de ITA tardaría cinco años en duplicar algo que ITA podría escribir en Lisp en tres meses, me refiero a cinco años si nada sale mal. De hecho, tal como funcionan las cosas en la mayoría de las empresas, cualquier proyecto de desarrollo que lleve cinco años probablemente nunca se termine.
Reconozco que este es un caso extremo. Los hackers de ITA parecen ser inusualmente inteligentes y C es un lenguaje de nivel bastante bajo. 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 en la que el jefe de pelo puntiagudo ni siquiera quiere pensar. Y por eso la mayoría de ellos no lo hacen. Porque, ya saben, cuando llega el momento, al jefe de pelo puntiagudo no le importa que le den una paliza a su empresa, siempre y cuando nadie pueda demostrar que es culpa suya. El plan más seguro para él personalmente es mantenerse cerca del centro de la manada.
En las grandes organizaciones, la frase que se utiliza para describir este enfoque es "la mejor práctica de la industria". Su propósito es proteger al jefe de pelo puntiagudo de toda responsabilidad: si elige algo que es "la mejor práctica de la industria" y la empresa pierde, no se le puede culpar. No fue él quien eligió, fue la industria.
Creo que este término se utilizó originalmente para describir métodos contables y demás. Lo que significa, en líneas generales, es no hacer nada extraño. Y en contabilidad, probablemente sea una buena idea. Los términos "de vanguardia" y "contabilidad" no suenan bien juntos. Pero cuando se importa este criterio a las decisiones sobre tecnología, se empiezan a obtener respuestas equivocadas.
La tecnología debería ser, a menudo, de vanguardia. En los lenguajes de programación, como ha señalado Erann Gat, lo que en realidad se consigue con las "mejores prácticas de la industria" no es lo mejor, sino simplemente el promedio. Cuando una decisión hace que se desarrolle un software a una fracción del ritmo de competidores más agresivos, "mejores prácticas" es un nombre inapropiado.
Aquí tenemos dos datos que creo que son muy valiosos. De hecho, lo sé por experiencia propia. En primer lugar, los lenguajes varían en potencia. En segundo lugar, la mayoría de los directivos ignoran esto deliberadamente. En conjunto, estos dos hechos son literalmente una receta para ganar dinero. ITA es un ejemplo de esta receta en acción. Si quieres triunfar en un negocio de software, simplemente afronta el problema más difícil que encuentres, utiliza el lenguaje más potente que puedas conseguir y espera a que los jefes de tus competidores, con su pelo puntiagudo, vuelvan a la media.
Apéndice: Poder
Como ilustración de lo que quiero decir sobre el poder relativo de los lenguajes de programación, considere el siguiente problema. Queremos escribir una función que genere acumuladores, una función que tome un número n y devuelva una función que tome otro número i y devuelva n incrementado en i.
(Esto se incrementa en , no en más. Un acumulador tiene que acumular).
En Common Lisp esto sería
(defun foo (n) (lambda (i) (incf ni)))
y en Perl 5,
sub foo { my ($n) = @_; sub {$n += shift} }
que tiene más elementos que la versión Lisp porque hay que extraer los parámetros manualmente en Perl.
En Smalltalk el código es un poco más largo que en Lisp
foo: n |s| s := n. ^[:i| s := s+i. ]
porque aunque en general las variables léxicas funcionan, no puedes hacer una asignación a un parámetro, por lo que debes crear una nueva variable s.
En Javascript, el ejemplo es, nuevamente, un poco más largo, porque Javascript conserva la distinción entre declaraciones y expresiones, por lo que necesita declaraciones de retorno explícitas para devolver valores:
function foo(n) { return function (i) { return n += i } }
(Para ser justos, Perl también conserva esta distinción, pero la trata de la manera típica de Perl al permitirle omitir los retornos).
Si intenta traducir el código Lisp/Perl/Smalltalk/Javascript a Python, se encontrará con algunas limitaciones. Debido a que Python no admite completamente las variables léxicas, debe crear una estructura de datos para almacenar el valor de n. Y aunque Python tiene un tipo de datos de función, no hay una representación literal para uno (a menos que el cuerpo sea solo una expresión única), por lo que debe crear una función con nombre para devolver. Esto es lo que obtendrá:
def foo(n): s = [n] def bar(i): s[0] += i return s[0] return bar
Los usuarios de Python podrían preguntar legítimamente por qué no pueden simplemente escribir
def foo(n): return lambda i: return n += i
o incluso
def foo(n): lambda i: n += i
Y supongo que probablemente lo harán algún día. (Pero si no quieren esperar a que Python evolucione hasta convertirse en Lisp, siempre podrían simplemente...)
En los lenguajes orientados a objetos, se puede simular, hasta cierto punto, un cierre (una función que hace referencia a variables definidas en ámbitos de inclusión) definiendo una clase con un método y un campo para reemplazar cada variable de un ámbito de inclusión. Esto hace que el programador realice el tipo de análisis de código que realizaría el compilador en un lenguaje con soporte completo para ámbitos léxicos, y no funcionará si más de una función hace referencia a la misma variable, pero es suficiente en casos simples como este.
Los expertos en Python parecen estar de acuerdo en que esta es la forma preferida de resolver el problema en Python, escribiendo
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 quisiera que los defensores de Python digan que estoy tergiversando el lenguaje, pero ambos me parecen más complejos que la primera versión. Estás haciendo lo mismo, configurando un lugar separado para almacenar el acumulador; es solo un campo en un objeto en lugar del encabezado de una lista. Y el uso de estos nombres de campo especiales y reservados, especialmente call , parece un poco complicado.
En la rivalidad entre Perl y Python, la afirmación de los hackers de Python parece ser que Python es una alternativa más elegante a Perl, pero lo que este caso demuestra es que la potencia es la máxima elegancia: el programa Perl es más simple (tiene menos elementos), aunque la sintaxis sea un poco más fea.
¿Qué ocurre con otros lenguajes? En los otros lenguajes mencionados en esta charla (Fortran, C, C++, Java y Visual Basic), no está claro si se puede resolver este problema. Ken Anderson dice que el siguiente código es lo más parecido que se puede conseguir 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 cumple con la especificación porque solo funciona con números enteros. Después de muchos intercambios de correo electrónico con hackers de Java, diría que escribir una versión polimórfica adecuada que se comporte como los ejemplos anteriores es algo entre muy complicado e imposible. Si alguien quiere escribir una, me interesaría mucho verla, pero yo personalmente me he quedado sin tiempo.
Por supuesto, no es literalmente cierto que no se pueda resolver este problema en otros lenguajes. El hecho de que todos estos lenguajes sean equivalentes a Turing significa que, estrictamente hablando, se puede escribir cualquier programa en cualquiera de ellos. ¿Cómo se haría entonces? 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 y en distintos grados en grandes proyectos de programación que existe un nombre para el fenómeno: la Décima Regla de Greenspun:
Cualquier programa en C o Fortran suficientemente complicado contiene una implementación lenta, informalmente especificada y plagada de errores, de la mitad de Common Lisp.
Si intentas resolver un problema difícil, la cuestión no es si utilizarás un lenguaje lo suficientemente potente, sino si (a) utilizarás un lenguaje potente, (b) escribirás un intérprete de facto para uno o (c) te convertirás en un compilador humano para uno. Vemos que esto ya está empezando a suceder en el ejemplo de Python, donde estamos simulando en efecto el código que generaría un compilador para implementar una variable léxica.
Esta práctica no sólo es común, sino que está institucionalizada. Por ejemplo, en el mundo de la programación orientada a objetos se oye hablar mucho de "patrones". Me pregunto si estos patrones no son a veces evidencia del caso (c), el compilador humano, en acción. Cuando veo patrones en mis programas, lo considero una señal de problemas. La forma de un programa debería reflejar sólo el problema que necesita resolver. Cualquier otra regularidad en el código es una señal, al menos para mí, de que estoy usando abstracciones que no son lo suficientemente potentes; a menudo, de que estoy generando a mano las expansiones de alguna macro que necesito escribir.
Notas
La CPU IBM 704 tenía aproximadamente el tamaño de un refrigerador, pero era mucho más pesada. La CPU pesaba 1490 kilos y los 4K de RAM estaban en una caja aparte que pesaba otros 1800 kilos. El Sub-Zero 690, uno de los refrigeradores domésticos más grandes, pesa 300 kilos.
Steve Russell también escribió el primer juego de computadora (digital), Spacewar, en 1962.
Si quieres engañar a un jefe de pelo puntiagudo para que te deje escribir software en Lisp, puedes intentar decirle que es XML.
Aquí está el generador de acumuladores en otros dialectos Lisp:
Scheme: (define (foo n) (lambda (i) (set! n (+ ni)) 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ó a abordar esta frase generalmente mal aplicada.
Peter Norvig descubrió que 16 de los 23 patrones en Design Patterns eran " invisibles o más simples " en Lisp.
Gracias a las muchas personas que respondieron a mis preguntas sobre varios idiomas y/o leyeron borradores de este libro, entre ellos Ken Anderson, Trevor Blackwell, Erann Gat, Dan Giffin, Sarah Harlin, Jeremy Hylton, Robert Morris, Peter Norvig, Guy Steele y Anton van Straaten. No son responsables de las opiniones expresadas.
Relacionado:
Mucha gente ha respondido a esta charla, por lo que he creado una página adicional para abordar los problemas que han planteado: Re: La venganza de los nerds .
También desencadenó un debate extenso y a menudo útil en la lista de correo LL1 . Véase en particular el correo de Anton van Straaten sobre la compresión semántica.
Algunos de los correos en LL1 me llevaron a intentar profundizar en el tema del poder del lenguaje en La concisión es poder .
Un conjunto más amplio de implementaciones canónicas del punto de referencia del generador de acumuladores se recopila en su propia página.
Traducción al japonés ,traducción al español , traducción al chino