Ser Popular
Mayo de 2001
(Este artículo fue escrito como una especie de plan de negocios para un nuevo lenguaje. Por lo tanto, carece (porque da por sentado) de la característica más importante de un buen lenguaje de programación: abstracciones muy potentes.)
Un amigo mío le dijo una vez a un eminente experto en sistemas operativos que quería diseñar un lenguaje de programación realmente bueno. El experto le dijo que sería una pérdida de tiempo, que los lenguajes de programación no se vuelven populares o impopulares basándose en sus méritos, y que, por lo tanto, sin importar lo bueno que fuera su lenguaje, nadie lo usaría. Al menos, eso era lo que le había sucedido al lenguaje que él había diseñado.
¿Qué hace que un lenguaje sea popular? ¿Los lenguajes populares merecen su popularidad? ¿Vale la pena intentar definir un buen lenguaje de programación? ¿Cómo lo harías?
Creo que las respuestas a estas preguntas se pueden encontrar observando a los hackers y aprendiendo lo que quieren. Los lenguajes de programación son para los hackers, y un lenguaje de programación es bueno como lenguaje de programación (en lugar de, por ejemplo, un ejercicio de semántica denotacional o diseño de compiladores) si y solo si a los hackers les gusta.
1 La mecánica de la popularidad
Es cierto, ciertamente, que la mayoría de la gente no elige lenguajes de programación basándose simplemente en sus méritos. A la mayoría de los programadores se les dice qué lenguaje usar por parte de otra persona. Y sin embargo, creo que el efecto de tales factores externos en la popularidad de los lenguajes de programación no es tan grande como a veces se piensa. Creo que un problema mayor es que la idea de un buen lenguaje de programación de un hacker no es la misma que la de la mayoría de los diseñadores de lenguajes.
Entre los dos, la opinión del hacker es la que importa. Los lenguajes de programación no son teoremas. Son herramientas, diseñadas para personas, y tienen que ser diseñadas para adaptarse a las fortalezas y debilidades humanas tanto como los zapatos tienen que ser diseñados para los pies humanos. Si un zapato aprieta cuando te lo pones, es un mal zapato, por muy elegante que sea como pieza de escultura.
Puede ser que la mayoría de los programadores no sepan distinguir un lenguaje bueno de uno malo. Pero eso no es diferente en ninguna otra herramienta. No significa que sea una pérdida de tiempo intentar diseñar un lenguaje bueno. Los hackers expertos saben reconocer un buen lenguaje cuando lo ven, y lo usarán. Los hackers expertos son una minoría minúscula, admitámoslo, pero esa minúscula minoría escribe todo el software bueno, y su influencia es tal que el resto de los programadores tenderán a usar el lenguaje que ellos usen. A menudo, de hecho, no es meramente influencia sino mandato: a menudo los hackers expertos son las mismas personas que, como sus jefes o asesores académicos, dicen a los otros programadores qué lenguaje usar.
La opinión de los hackers expertos no es la única fuerza que determina la popularidad relativa de los lenguajes de programación —el software heredado (Cobol) y el bombo publicitario (Ada, Java) también juegan un papel—, pero creo que es la fuerza más poderosa a largo plazo. Dado un masa crítica inicial y suficiente tiempo, un lenguaje de programación probablemente se vuelve tan popular como merece ser. Y la popularidad separa aún más los lenguajes buenos de los malos, porque la retroalimentación de usuarios reales siempre conduce a mejoras. Mira cuánto ha cambiado cualquier lenguaje popular durante su vida. Perl y Fortran son casos extremos, pero incluso Lisp ha cambiado mucho. Lisp 1.5 no tenía macros, por ejemplo; estas evolucionaron más tarde, después de que los hackers del MIT hubieran pasado un par de años usando Lisp para escribir programas reales. [1]
Así que, ya sea que un lenguaje tenga que ser bueno para ser popular o no, creo que un lenguaje tiene que ser popular para ser bueno. Y tiene que seguir siendo popular para seguir siendo bueno. El estado del arte en lenguajes de programación no se detiene. Y sin embargo, los Lisp que tenemos hoy en día siguen siendo bastante parecidos a lo que tenían en el MIT a mediados de los 80, porque esa es la última vez que Lisp tuvo una base de usuarios suficientemente grande y exigente.
Por supuesto, los hackers tienen que saber sobre un lenguaje antes de poder usarlo. ¿Cómo se enteran? Por otros hackers. Pero tiene que haber algún grupo inicial de hackers usando el lenguaje para que otros siquiera se enteren. Me pregunto qué tan grande tiene que ser este grupo; ¿cuántos usuarios constituyen una masa crítica? A ojo de buen cubero, diría que veinte. Si un lenguaje tuviera veinte usuarios separados, es decir, veinte usuarios que decidieran por sí mismos usarlo, lo consideraría real.
Llegar allí no puede ser fácil. No me sorprendería si fuera más difícil pasar de cero a veinte que de veinte a mil. La mejor manera de conseguir esos veinte usuarios iniciales es probablemente usar un caballo de Troya: darle a la gente una aplicación que quieren, que resulta estar escrita en el nuevo lenguaje.
2 Factores externos
Empecemos reconociendo un factor externo que sí afecta la popularidad de un lenguaje de programación. Para volverse popular, un lenguaje de programación tiene que ser el lenguaje de scripting de un sistema popular. Fortran y Cobol fueron los lenguajes de scripting de los primeros mainframes de IBM. C fue el lenguaje de scripting de Unix, y, más tarde, Perl. Tcl es el lenguaje de scripting de Tk. Java y Javascript están destinados a ser los lenguajes de scripting de los navegadores web.
Lisp no es un lenguaje masivamente popular porque no es el lenguaje de scripting de un sistema masivamente popular. La popularidad que conserva se remonta a los años 60 y 70, cuando era el lenguaje de scripting del MIT. Muchos de los grandes programadores de la época estuvieron asociados con el MIT en algún momento. Y a principios de los 70, antes de C, el dialecto de Lisp del MIT, llamado MacLisp, era uno de los únicos lenguajes de programación que un hacker serio querría usar.
Hoy en día Lisp es el lenguaje de scripting de dos sistemas moderadamente populares, Emacs y Autocad, y por esa razón sospecho que la mayor parte de la programación en Lisp que se hace hoy en día se hace en Emacs Lisp o AutoLisp.
Los lenguajes de programación no existen de forma aislada. Hackear es un verbo transitivo —los hackers suelen estar hackeando algo— y en la práctica los lenguajes se juzgan en relación con aquello para lo que se usan. Así que si quieres diseñar un lenguaje popular, o bien tienes que proporcionar más que un lenguaje, o bien tienes que diseñar tu lenguaje para reemplazar el lenguaje de scripting de algún sistema existente.
Common Lisp es impopular en parte porque es un huérfano. Originalmente venía con un sistema para hackear: la Lisp Machine. Pero las Lisp Machines (junto con las computadoras paralelas) fueron aplastadas por el creciente poder de los procesadores de propósito general en los años 80. Common Lisp podría haber seguido siendo popular si hubiera sido un buen lenguaje de scripting para Unix. Es, por desgracia, uno terriblemente malo.
Una forma de describir esta situación es decir que un lenguaje no se juzga por sus propios méritos. Otra perspectiva es que un lenguaje de programación realmente no es un lenguaje de programación a menos que también sea el lenguaje de scripting de algo. Esto solo parece injusto si viene como una sorpresa. Creo que no es más injusto que esperar que un lenguaje de programación tenga, por ejemplo, una implementación. Es solo parte de lo que es un lenguaje de programación.
Un lenguaje de programación necesita una buena implementación, por supuesto, y esta debe ser gratuita. Las empresas pagarán por el software, pero los hackers individuales no, y son a los hackers a quienes necesitas atraer.
Un lenguaje también necesita tener un libro sobre él. El libro debe ser delgado, bien escrito y lleno de buenos ejemplos. K&R es el ideal aquí. En este momento casi diría que un lenguaje tiene que tener un libro publicado por O'Reilly. Esa se está convirtiendo en la prueba de que importa a los hackers.
También debe haber documentación en línea. De hecho, el libro puede comenzar como documentación en línea. Pero no creo que los libros físicos estén obsoletos todavía. Su formato es conveniente, y la censura de facto impuesta por los editores es un filtro útil, aunque imperfecto. Las librerías son uno de los lugares más importantes para aprender sobre nuevos lenguajes.
3 Brevedad
Dado que puedes proporcionar las tres cosas que cualquier lenguaje necesita —una implementación gratuita, un libro y algo para hackear—, ¿cómo haces un lenguaje que les guste a los hackers?
Una cosa que les gusta a los hackers es la brevedad. Los hackers son perezosos, de la misma manera que los matemáticos y los arquitectos modernistas son perezosos: odian todo lo extralimitado. No estaría lejos de la verdad decir que un hacker a punto de escribir un programa decide qué lenguaje usar, al menos subconscientemente, basándose en el número total de caracteres que tendrá que escribir. Si no es precisamente así como piensan los hackers, un diseñador de lenguajes haría bien en actuar como si lo fuera.
Es un error intentar mimar al usuario con expresiones largas que pretenden parecerse al inglés. Cobol es notorio por este defecto. Un hacker consideraría que se le pide escribir
suma x a y dando z
en lugar de
z = x+y
como algo entre un insulto a su inteligencia y un pecado contra Dios.
A veces se ha dicho que Lisp debería usar first y rest en lugar de car y cdr, porque haría los programas más fáciles de leer. Quizás durante las primeras dos horas. Pero un hacker puede aprender lo suficientemente rápido que car significa el primer elemento de una lista y cdr significa el resto. Usar first y rest significa un 50% más de escritura. Y también tienen longitudes diferentes, lo que significa que los argumentos no se alinearán cuando se llamen, como a menudo lo hacen car y cdr, en líneas sucesivas. He descubierto que la forma en que el código se alinea en la página importa mucho. Apenas puedo leer código Lisp cuando está en una fuente de ancho variable, y los amigos dicen que esto también es cierto para otros lenguajes.
La brevedad es un lugar donde los lenguajes fuertemente tipados pierden. Todas las demás cosas iguales, nadie quiere empezar un programa con un montón de declaraciones. Todo lo que pueda ser implícito, debe serlo.
Los tokens individuales también deben ser cortos. Perl y Common Lisp ocupan polos opuestos en esta cuestión. Los programas de Perl pueden ser casi crípticamente densos, mientras que los nombres de los operadores integrados de Common Lisp son cómicamente largos. Los diseñadores de Common Lisp probablemente esperaban que los usuarios tuvieran editores de texto que escribieran estos nombres largos por ellos. Pero el costo de un nombre largo no es solo el costo de escribirlo. También está el costo de leerlo, y el costo del espacio que ocupa en tu pantalla.
4 Hackeabilidad
Hay una cosa más importante que la brevedad para un hacker: poder hacer lo que quieres. En la historia de los lenguajes de programación, una cantidad sorprendente de esfuerzo se ha dedicado a evitar que los programadores hagan cosas consideradas impropias. Este es un plan peligrosamente presuntuoso. ¿Cómo puede el diseñador del lenguaje saber lo que el programador necesitará hacer? Creo que los diseñadores de lenguajes harían mejor en considerar a su usuario objetivo como un genio que necesitará hacer cosas que ellos nunca anticiparon, en lugar de un torpe al que hay que proteger de sí mismo. El torpe se disparará en el pie de todos modos. Puedes salvarlo de referirse a variables en otro paquete, pero no puedes salvarlo de escribir un programa mal diseñado para resolver el problema equivocado, y tardar una eternidad en hacerlo.
Los buenos programadores a menudo quieren hacer cosas peligrosas y poco recomendables. Por poco recomendables me refiero a cosas que van más allá de la fachada semántica que el lenguaje intenta presentar: obtener la representación interna de alguna abstracción de alto nivel, por ejemplo. A los hackers les gusta hackear, y hackear significa meterse dentro de las cosas y adivinar el diseñador original.
Permítete ser adivinado. Cuando creas cualquier herramienta, la gente la usa de maneras que no pretendías, y esto es especialmente cierto para una herramienta altamente articulada como un lenguaje de programación. Muchos hackers querrán modificar tu modelo semántico de una manera que nunca imaginaste. Yo digo, déjalos; dale al programador acceso a tantas cosas internas como puedas sin poner en peligro los sistemas de tiempo de ejecución como el recolector de basura.
En Common Lisp a menudo he querido iterar a través de los campos de una struct —para eliminar referencias a un objeto eliminado, por ejemplo, o encontrar campos que no están inicializados. Sé que las structs son solo vectores debajo. Y sin embargo, no puedo escribir una función de propósito general que pueda llamar en cualquier struct. Solo puedo acceder a los campos por nombre, porque eso es lo que se supone que significa una struct.
Un hacker puede querer subvertir el modelo previsto de las cosas solo una o dos veces en un programa grande. Pero qué diferencia hace poder hacerlo. Y puede ser más que una cuestión de simplemente resolver un problema. Hay una especie de placer aquí también. Los hackers comparten el placer secreto del cirujano al hurgar en las entrañas gruesas, el placer secreto del adolescente al reventarse los granos. [2] Para los chicos, al menos, ciertos tipos de horrores son fascinantes. La revista Maxim publica un volumen anual de fotografías, que contiene una mezcla de pin-ups y accidentes espantosos. Conocen a su público.
Históricamente, Lisp ha sido bueno para dejar que los hackers hagan lo que quieran. La corrección política de Common Lisp es una aberración. Los primeros Lisp te permitían acceder a todo. Una gran parte de ese espíritu está, afortunadamente, preservada en las macros. Qué cosa tan maravillosa, poder hacer transformaciones arbitrarias en el código fuente.
Las macros clásicas son una herramienta real de hacker —simples, potentes y peligrosas. Es tan fácil entender lo que hacen: llamas a una función con los argumentos de la macro, y lo que sea que devuelva se inserta en lugar de la llamada a la macro. Las macros higiénicas encarnan el principio opuesto. Intentan protegerte de entender lo que están haciendo. Nunca he oído explicar las macros higiénicas en una sola frase. Y son un ejemplo clásico de los peligros de decidir lo que los programadores tienen permitido querer. Las macros higiénicas están destinadas a protegerme de la captura de variables, entre otras cosas, pero la captura de variables es exactamente lo que quiero en algunas macros.
Un lenguaje realmente bueno debe ser a la vez limpio y sucio: limpiamente diseñado, con un núcleo pequeño de operadores bien entendidos y altamente ortogonales, pero sucio en el sentido de que permite a los hackers hacer lo que quieran con él. C es así. Los primeros Lisp también lo eran. Un lenguaje de hacker real siempre tendrá un carácter ligeramente descarado.
Un buen lenguaje de programación debería tener características que hagan que el tipo de personas que usan la frase "ingeniería de software" nieguen con la cabeza desaprobadoramente. En el otro extremo del continuo están lenguajes como Ada y Pascal, modelos de propiedad que son buenos para enseñar y poco más.
5 Programas desechables
Para ser atractivo para los hackers, un lenguaje debe ser bueno para escribir los tipos de programas que quieren escribir. Y eso significa, quizás sorprendentemente, que tiene que ser bueno para escribir programas desechables.
Un programa desechable es un programa que escribes rápidamente para una tarea limitada: un programa para automatizar alguna tarea de administración del sistema, o generar datos de prueba para una simulación, o convertir datos de un formato a otro. Lo sorprendente de los programas desechables es que, al igual que los edificios "temporales" construidos en muchas universidades estadounidenses durante la Segunda Guerra Mundial, a menudo no se desechan. Muchos evolucionan a programas reales, con características reales y usuarios reales.
Tengo la corazonada de que los mejores programas grandes comienzan así, en lugar de ser diseñados grandes desde el principio, como la presa Hoover. Es aterrador construir algo grande desde cero. Cuando las personas asumen un proyecto demasiado grande, se sienten abrumadas. El proyecto se atasca, o el resultado es estéril y rígido: un centro comercial en lugar de un centro real, Brasilia en lugar de Roma, Ada en lugar de C.
Otra forma de obtener un programa grande es empezar con un programa desechable y seguir mejorándolo. Este enfoque es menos desalentador, y el diseño del programa se beneficia de la evolución. Creo que, si uno mirara, esta sería la forma en que se desarrollaron la mayoría de los programas grandes. Y los que evolucionaron de esta manera probablemente todavía se escriben en el lenguaje en el que se escribieron por primera vez, porque es raro que un programa se porte, excepto por razones políticas. Y así, paradójicamente, si quieres hacer un lenguaje que se use para sistemas grandes, tienes que hacerlo bueno para escribir programas desechables, porque de ahí provienen los sistemas grandes.
Perl es un ejemplo llamativo de esta idea. No solo fue diseñado para escribir programas desechables, sino que él mismo fue prácticamente un programa desechable. Perl comenzó como una colección de utilidades para generar informes, y solo evolucionó a un lenguaje de programación a medida que los programas desechables que la gente escribía en él crecían. No fue hasta Perl 5 (si acaso) que el lenguaje fue adecuado para escribir programas serios, y sin embargo ya era masivamente popular.
¿Qué hace que un lenguaje sea bueno para programas desechables? Para empezar, debe estar fácilmente disponible. Un programa desechable es algo que esperas escribir en una hora. Por lo tanto, el lenguaje probablemente ya debe estar instalado en la computadora que estás usando. No puede ser algo que tengas que instalar antes de usarlo. Tiene que estar ahí. C estaba ahí porque venía con el sistema operativo. Perl estaba ahí porque originalmente era una herramienta para administradores de sistemas, y el tuyo ya lo había instalado.
Estar disponible significa más que estar instalado, sin embargo. Un lenguaje interactivo, con una interfaz de línea de comandos, es más disponible que uno que tienes que compilar y ejecutar por separado. Un lenguaje de programación popular debe ser interactivo y arrancar rápido.
Otra cosa que quieres en un programa desechable es brevedad. La brevedad siempre es atractiva para los hackers, y nunca más que en un programa que esperan terminar en una hora.
6 Bibliotecas
Por supuesto, lo último en brevedad es tener el programa ya escrito para ti, y simplemente llamarlo. Y esto nos lleva a lo que creo que será una característica cada vez más importante de los lenguajes de programación: las funciones de biblioteca. Perl gana porque tiene grandes bibliotecas para manipular cadenas. Esta clase de funciones de biblioteca son especialmente importantes para los programas desechables, que a menudo se escriben originalmente para convertir o extraer datos. Muchos programas de Perl probablemente comienzan como un par de llamadas a bibliotecas unidas.
Creo que muchos de los avances que ocurran en los lenguajes de programación en los próximos cincuenta años tendrán que ver con las funciones de biblioteca. Creo que los futuros lenguajes de programación tendrán bibliotecas tan cuidadosamente diseñadas como el núcleo del lenguaje. El diseño de lenguajes de programación no tratará sobre si hacer tu lenguaje fuertemente o débilmente tipado, o orientado a objetos, o funcional, o lo que sea, sino sobre cómo diseñar grandes bibliotecas. El tipo de diseñadores de lenguajes a los que les gusta pensar en cómo diseñar sistemas de tipos pueden estremecerse ante esto. ¡Es casi como escribir aplicaciones! Lástima. Los lenguajes son para programadores, y las bibliotecas son lo que los programadores necesitan.
Es difícil diseñar buenas bibliotecas. No se trata simplemente de escribir mucho código. Una vez que las bibliotecas se vuelven demasiado grandes, a veces puede llevar más tiempo encontrar la función que necesitas que escribir el código tú mismo. Las bibliotecas deben diseñarse utilizando un pequeño conjunto de operadores ortogonales, al igual que el núcleo del lenguaje. Debería ser posible que el programador adivine qué llamada a biblioteca hará lo que necesita.
Las bibliotecas son un área en la que Common Lisp falla. Solo hay bibliotecas rudimentarias para manipular cadenas, y casi ninguna para interactuar con el sistema operativo. Por razones históricas, Common Lisp intenta pretender que el SO no existe. Y como no puedes hablar con el SO, es poco probable que puedas escribir un programa serio usando solo los operadores integrados en Common Lisp. También tienes que usar algunos trucos específicos de la implementación, y en la práctica estos tienden a no darte todo lo que quieres. Los hackers pensarían mucho más en Lisp si Common Lisp tuviera potentes bibliotecas de cadenas y buen soporte para el SO.
7 Sintaxis
¿Podría un lenguaje con la sintaxis de Lisp, o más precisamente, la falta de sintaxis, volverse popular alguna vez? No sé la respuesta a esta pregunta. Sí creo que la sintaxis no es la razón principal por la que Lisp no es popular actualmente. Common Lisp tiene problemas peores que la sintaxis desconocida. Conozco a varios programadores que se sienten cómodos con la sintaxis prefija y, sin embargo, usan Perl por defecto, porque tiene potentes bibliotecas de cadenas y puede hablar con el SO.
Hay dos problemas posibles con la notación prefija: que sea desconocida para los programadores y que no sea lo suficientemente densa. La sabiduría convencional en el mundo Lisp es que el primer problema es el real. No estoy tan seguro. Sí, la notación prefija hace que los programadores ordinarios entren en pánico. Pero no creo que las opiniones de los programadores ordinarios importen. Los lenguajes se vuelven populares o impopulares basándose en lo que los hackers expertos piensan de ellos, y creo que los hackers expertos podrían lidiar con la notación prefija. La sintaxis de Perl puede ser bastante incomprensible, pero eso no ha impedido la popularidad de Perl. Si acaso, puede haber ayudado a fomentar un culto a Perl.
Un problema más serio es la difusividad de la notación prefija. Para los hackers expertos, eso es realmente un problema. Nadie quiere escribir (aref a x y) cuando podría escribir a[x,y].
En este caso particular hay una forma de sortear el problema. Si tratamos las estructuras de datos como si fueran funciones sobre índices, podríamos escribir (a x y) en su lugar, lo que es aún más corto que la forma de Perl. Trucos similares pueden acortar otros tipos de expresiones.
Podemos eliminar (o hacer opcional) muchos paréntesis haciendo que la indentación sea significativa. Así es como los programadores leen el código de todos modos: cuando la indentación dice una cosa y los delimitadores dicen otra, nos guiamos por la indentación. Tratar la indentación como significativa eliminaría esta fuente común de errores, además de hacer los programas más cortos.
A veces la sintaxis infija es más fácil de leer. Esto es especialmente cierto para las expresiones matemáticas. He usado Lisp toda mi vida de programador y todavía no encuentro las expresiones matemáticas prefijas naturales. Y sin embargo, es conveniente, especialmente cuando se genera código, tener operadores que toman cualquier número de argumentos. Así que si tenemos sintaxis infija, probablemente debería implementarse como algún tipo de macro de lectura.
No creo que debamos oponernos religiosamente a introducir sintaxis en Lisp, siempre y cuando se traduzca de una manera bien entendida en s-expresiones subyacentes. Ya hay una buena cantidad de sintaxis en Lisp. No es necesariamente malo introducir más, siempre y cuando nadie se vea obligado a usarla. En Common Lisp, algunos delimitadores están reservados para el lenguaje, lo que sugiere que al menos algunos de los diseñadores tenían la intención de tener más sintaxis en el futuro.
Una de las piezas de sintaxis más egregiamente no-lispy en Common Lisp se encuentra en las cadenas de formato; format es un lenguaje en sí mismo, y ese lenguaje no es Lisp. Si hubiera un plan para introducir más sintaxis en Lisp, los especificadores de formato podrían incluirse en él. Sería bueno que las macros pudieran generar especificadores de formato de la misma manera que generan cualquier otro tipo de código.
Un eminente hacker de Lisp me dijo que su copia de CLTL se abre en la sección de formato. La mía también. Esto probablemente indica margen de mejora. También puede significar que los programas realizan muchas operaciones de E/S.
8 Eficiencia
Un buen lenguaje, como todo el mundo sabe, debería generar código rápido. Pero en la práctica, no creo que el código rápido provenga principalmente de las cosas que haces en el diseño del lenguaje. Como Knuth señaló hace mucho tiempo, la velocidad solo importa en ciertos cuellos de botella críticos. Y como muchos programadores han observado desde entonces, uno se equivoca muy a menudo sobre dónde están estos cuellos de botella.
Así que, en la práctica, la forma de obtener código rápido es tener un muy buen perfilador, en lugar de, por ejemplo, hacer que el lenguaje sea fuertemente tipado. No necesitas conocer el tipo de cada argumento en cada llamada del programa. Sí necesitas poder declarar los tipos de argumentos en los cuellos de botella. Y aún más, necesitas poder averiguar dónde están los cuellos de botella.
Una queja que la gente ha tenido con Lisp es que es difícil saber qué es caro. Esto podría ser cierto. También podría ser inevitable, si quieres tener un lenguaje muy abstracto. Y en cualquier caso, creo que un buen perfilado haría mucho para solucionar el problema: pronto aprenderías qué es caro.
Parte del problema aquí es social. A los diseñadores de lenguajes les gusta escribir compiladores rápidos. Así es como miden su habilidad. Piensan en el perfilador como un complemento, en el mejor de los casos. Pero en la práctica, un buen perfilador puede hacer más para mejorar la velocidad de los programas reales escritos en el lenguaje que un compilador que genera código rápido. Aquí, de nuevo, los diseñadores de lenguajes están algo fuera de contacto con sus usuarios. Hacen un muy buen trabajo resolviendo el problema ligeramente equivocado.
Podría ser una buena idea tener un perfilador activo —enviar datos de rendimiento al programador en lugar de esperar a que él los pida. Por ejemplo, el editor podría mostrar los cuellos de botella en rojo cuando el programador edita el código fuente. Otro enfoque sería representar de alguna manera lo que está sucediendo en los programas en ejecución. Esto sería una gran ventaja, especialmente en aplicaciones basadas en servidores, donde tienes muchos programas en ejecución para observar. Un perfilador activo podría mostrar gráficamente lo que está sucediendo en la memoria mientras un programa se ejecuta, o incluso hacer sonidos que indiquen lo que está sucediendo.
El sonido es una buena señal de problemas. En un lugar donde trabajé, teníamos un gran tablero de diales que mostraba lo que estaba sucediendo con nuestros servidores web. Las manecillas eran movidas por pequeños servomotores que hacían un ligero ruido al girar. No podía ver el tablero desde mi escritorio, pero descubrí que podía saber inmediatamente, por el sonido, cuándo había un problema con un servidor.
Incluso podría ser posible escribir un perfilador que detectara automáticamente algoritmos ineficientes. No me sorprendería si ciertos patrones de acceso a la memoria resultaran ser signos seguros de malos algoritmos. Si hubiera un pequeño tipo corriendo dentro de la computadora ejecutando nuestros programas, probablemente tendría una historia tan larga y lastimera que contar sobre su trabajo como un empleado del gobierno federal. A menudo tengo la sensación de que estoy enviando al procesador a muchas búsquedas inútiles, pero nunca he tenido una buena manera de ver lo que está haciendo.
Varios Lisp ahora compilan a bytecode, que luego es ejecutado por un intérprete. Esto generalmente se hace para facilitar la portabilidad de la implementación, pero podría ser una característica útil del lenguaje. Podría ser una buena idea hacer del bytecode una parte oficial del lenguaje, y permitir a los programadores usar bytecode en línea en los cuellos de botella. Entonces esas optimizaciones también serían portátiles.
La naturaleza de la velocidad, tal como la percibe el usuario final, puede estar cambiando. Con el auge de las aplicaciones basadas en servidores, cada vez más programas pueden resultar limitados por E/S. Valdrá la pena hacer que la E/S sea rápida. El lenguaje puede ayudar con medidas sencillas como funciones de salida formateada simples y rápidas, y también con cambios estructurales profundos como el almacenamiento en caché y los objetos persistentes.
Los usuarios están interesados en el tiempo de respuesta. Pero otro tipo de eficiencia será cada vez más importante: el número de usuarios simultáneos que puedes soportar por procesador. Muchas de las aplicaciones interesantes que se escribirán en el futuro cercano serán aplicaciones basadas en servidores, y el número de usuarios por servidor es la pregunta crítica para cualquiera que ofrezca tales aplicaciones. En el costo de capital de una empresa que ofrece una aplicación basada en servidores, este es el divisor.
Durante años, la eficiencia no ha importado mucho en la mayoría de las aplicaciones para usuarios finales. Los desarrolladores han podido asumir que cada usuario tendría un procesador cada vez más potente en su escritorio. Y según la Ley de Parkinson, el software se ha expandido para usar los recursos disponibles. Eso cambiará con las aplicaciones basadas en servidores. En ese mundo, el hardware y el software se suministrarán juntos. Para las empresas que ofrecen aplicaciones basadas en servidores, marcará una gran diferencia en el resultado final cuántos usuarios pueden soportar por servidor.
En algunas aplicaciones, el procesador será el factor limitante, y la velocidad de ejecución será lo más importante a optimizar. Pero a menudo la memoria será el límite; el número de usuarios simultáneos estará determinado por la cantidad de memoria que necesites para los datos de cada usuario. El lenguaje también puede ayudar aquí. Un buen soporte para hilos permitirá que todos los usuarios compartan un único heap. También puede ser útil tener objetos persistentes y/o soporte a nivel de lenguaje para la carga diferida.
9 Tiempo
El último ingrediente que necesita un lenguaje popular es el tiempo. Nadie quiere escribir programas en un lenguaje que pueda desaparecer, como lo hacen tantos lenguajes de programación. Por lo tanto, la mayoría de los hackers tenderán a esperar a que un lenguaje exista durante un par de años antes de siquiera considerar usarlo.
Los inventores de cosas nuevas y maravillosas a menudo se sorprenden al descubrir esto, pero se necesita tiempo para que cualquier mensaje llegue a la gente. Un amigo mío rara vez hace algo la primera vez que se lo piden. Sabe que la gente a veces pide cosas que luego resulta que no quieren. Para evitar perder su tiempo, espera hasta la tercera o cuarta vez que se lo piden; para entonces, quien se lo pida puede estar bastante molesto, pero al menos probablemente realmente quieren lo que piden.
La mayoría de la gente ha aprendido a hacer un tipo similar de filtrado en las cosas nuevas de las que oyen hablar. Ni siquiera empiezan a prestar atención hasta que han oído hablar de algo diez veces. Están perfectamente justificados: la mayoría de los nuevos y populares "lo que sea" resultan ser una pérdida de tiempo, y eventualmente desaparecen. Al retrasar el aprendizaje de VRML, evité tener que aprenderlo por completo.
Así que cualquiera que invente algo nuevo tiene que esperar seguir repitiendo su mensaje durante años antes de que la gente empiece a entenderlo. Escribimos lo que fue, hasta donde yo sé, la primera aplicación basada en servidor web, y nos llevó años hacer entender a la gente que no tenía que descargarse. No es que fueran estúpidos. Simplemente nos tenían en "mute".
La buena noticia es que la simple repetición resuelve el problema. Todo lo que tienes que hacer es seguir contando tu historia, y eventualmente la gente empezará a escuchar. No es cuando la gente se da cuenta de que estás ahí que prestan atención; es cuando se dan cuenta de que sigues ahí.
Bien está que normalmente se necesite tiempo para coger impulso. La mayoría de las tecnologías evolucionan mucho incluso después de su lanzamiento inicial —especialmente los lenguajes de programación—. Nada podría ser mejor, para una nueva tecnología, que unos años de ser utilizada solo por un pequeño número de adoptantes tempranos. Los adoptantes tempranos son sofisticados y exigentes, y eliminan rápidamente las fallas que quedan en tu tecnología. Cuando solo tienes unos pocos usuarios, puedes estar en contacto cercano con todos ellos. Y los adoptantes tempranos son indulgentes cuando mejoras tu sistema, incluso si esto causa algunas roturas.
Hay dos formas en que se introduce nueva tecnología: el método de crecimiento orgánico y el método del gran estallido. El método de crecimiento orgánico se ejemplifica con la clásica startup de garaje subfinanciada y "a ojo de buen cubero". Un par de tipos, trabajando en la oscuridad, desarrollan una nueva tecnología. La lanzan sin marketing y inicialmente tienen solo unos pocos usuarios (fanáticamente devotos). Continúan mejorando la tecnología, y mientras tanto su base de usuarios crece por el boca a boca. Antes de que se den cuenta, son grandes.
El otro enfoque, el método del gran estallido, se ejemplifica con la startup respaldada por capital de riesgo y fuertemente comercializada. Se apresuran a desarrollar un producto, lo lanzan con gran publicidad e inmediatamente (esperan) tienen una gran base de usuarios.
Generalmente, los chicos del garaje envidian a los chicos del gran estallido. Los chicos del gran estallido son pulcros y confiados y respetados por los capitalistas de riesgo. Pueden permitirse lo mejor de todo, y la campaña de relaciones públicas que rodea el lanzamiento tiene el efecto secundario de convertirlos en celebridades. Los chicos del crecimiento orgánico, sentados en su garaje, se sienten pobres y poco queridos. Y sin embargo, creo que a menudo se equivocan al compadecerse de sí mismos. El crecimiento orgánico parece producir mejor tecnología y fundadores más ricos que el método del gran estallido. Si miras las tecnologías dominantes de hoy, encontrarás que la mayoría de ellas crecieron orgánicamente.
Este patrón no solo se aplica a las empresas. También lo ves en la investigación patrocinada. Multics y Common Lisp fueron proyectos de gran estallido, y Unix y MacLisp fueron proyectos de crecimiento orgánico.
10 Rediseño
"La mejor escritura es reescritura", escribió E. B. White. Todo buen escritor lo sabe, y esto también es cierto para el software. La parte más importante del diseño es el rediseño. Los lenguajes de programación, especialmente, no se rediseñan lo suficiente.
Para escribir buen software, debes mantener simultáneamente dos ideas opuestas en tu cabeza. Necesitas la fe ingenua del hacker joven en sus habilidades, y al mismo tiempo el escepticismo del veterano. Tienes que ser capaz de pensar qué tan difícil puede ser? con una mitad de tu cerebro mientras piensas nunca funcionará con la otra.
El truco es darse cuenta de que no hay una contradicción real aquí. Quieres ser optimista y escéptico sobre dos cosas diferentes. Tienes que ser optimista sobre la posibilidad de resolver el problema, pero escéptico sobre el valor de cualquier solución que tengas hasta ahora.
Las personas que hacen buen trabajo a menudo piensan que cualquier cosa en la que estén trabajando no es buena. Otros ven lo que han hecho y están llenos de asombro, pero el creador está lleno de preocupación. Este patrón no es una coincidencia: es la preocupación lo que hizo que el trabajo fuera bueno.
Si puedes mantener la esperanza y la preocupación equilibradas, impulsarán un proyecto hacia adelante de la misma manera que tus dos piernas impulsan una bicicleta hacia adelante. En la primera fase del motor de innovación de dos ciclos, trabajas furiosamente en algún problema, inspirado por tu confianza en que podrás resolverlo. En la segunda fase, miras lo que has hecho a la cruda luz de la mañana, y ves todos sus defectos muy claramente. Pero mientras tu espíritu crítico no supere tu esperanza, podrás mirar tu sistema, admitidamente incompleto, y pensar, ¿qué tan difícil puede ser llegar al resto del camino?, continuando así el ciclo.
Es complicado mantener las dos fuerzas equilibradas. En los hackers jóvenes, predomina el optimismo. Producen algo, están convencidos de que es genial, y nunca lo mejoran. En los hackers viejos, predomina el escepticismo, y ni siquiera se atreven a emprender proyectos ambiciosos.
Cualquier cosa que puedas hacer para mantener el ciclo de rediseño en marcha es buena. La prosa se puede reescribir una y otra vez hasta que estés satisfecho con ella. Pero el software, por regla general, no se rediseña lo suficiente. La prosa tiene lectores, pero el software tiene usuarios. Si un escritor reescribe un ensayo, es poco probable que las personas que leyeron la versión anterior se quejen de que sus pensamientos se han roto por alguna incompatibilidad recién introducida.
Los usuarios son un arma de doble filo. Pueden ayudarte a mejorar tu lenguaje, pero también pueden disuadirte de mejorarlo. Así que elige a tus usuarios cuidadosamente, y sé lento en aumentar su número. Tener usuarios es como la optimización: el curso sabio es retrasarlo. Además, como regla general, puedes salirte con la tuya cambiando más de lo que crees en cualquier momento dado. Introducir un cambio es como quitar una tirita: el dolor es un recuerdo casi tan pronto como lo sientes.
Todo el mundo sabe que no es una buena idea tener un lenguaje diseñado por un comité. Los comités producen malos diseños. Pero creo que el peor peligro de los comités es que interfieren con el rediseño. Es tanto trabajo introducir cambios que nadie quiere molestarse. Lo que sea que decida un comité tiende a quedarse así, incluso si a la mayoría de los miembros no les gusta.
Incluso un comité de dos personas se interpone en el camino del rediseño. Esto ocurre particularmente en las interfaces entre piezas de software escritas por dos personas diferentes. Para cambiar la interfaz, ambos tienen que acordar cambiarla a la vez. Y así, las interfaces tienden a no cambiar en absoluto, lo que es un problema porque tienden a ser una de las partes más ad hoc de cualquier sistema.
Una solución aquí podría ser diseñar sistemas de manera que las interfaces sean horizontales en lugar de verticales —de modo que los módulos sean siempre estratos de abstracción apilados verticalmente—. Entonces la interfaz tenderá a ser propiedad de uno de ellos. El nivel inferior de dos niveles será un lenguaje en el que está escrito el superior, en cuyo caso el nivel inferior será el propietario de la interfaz, o será un esclavo, en cuyo caso la interfaz puede ser dictada por el nivel superior.
11 Lisp
Lo que todo esto implica es que hay esperanza para un nuevo Lisp. Hay esperanza para cualquier lenguaje que les dé a los hackers lo que quieren, incluido Lisp. Creo que podríamos haber cometido un error al pensar que a los hackers les repele la rareza de Lisp. Esta cómoda ilusión puede habernos impedido ver el problema real con Lisp, o al menos con Common Lisp, que es que apesta para hacer lo que los hackers quieren hacer. Un lenguaje de hacker necesita bibliotecas potentes y algo para hackear. Common Lisp no tiene ninguna. Un lenguaje de hacker es conciso y hackeable. Common Lisp no lo es.
La buena noticia es que no es Lisp lo que apesta, sino Common Lisp. Si podemos desarrollar un nuevo Lisp que sea un lenguaje de hacker, creo que los hackers lo usarán. Usarán cualquier lenguaje que haga el trabajo. Todo lo que tenemos que hacer es asegurarnos de que este nuevo Lisp haga algún trabajo importante mejor que otros lenguajes.
La historia ofrece cierto aliento. Con el tiempo, los sucesivos nuevos lenguajes de programación han tomado cada vez más características de Lisp. Ya no queda mucho por copiar antes de que el lenguaje que has creado sea Lisp. El último lenguaje de moda, Python, es un Lisp aguado con sintaxis infija y sin macros. Un nuevo Lisp sería un paso natural en esta progresión.
A veces pienso que sería un buen truco de marketing llamarlo una versión mejorada de Python. Eso suena más moderno que Lisp. Para mucha gente, Lisp es un lenguaje de IA lento con muchos paréntesis. La biografía oficial de Fritz Kunze evita cuidadosamente mencionar la palabra L. Pero mi suposición es que no deberíamos tener miedo de llamar al nuevo Lisp Lisp. Lisp todavía tiene mucho respeto latente entre los mejores hackers —los que tomaron 6.001 y lo entendieron, por ejemplo—. Y esos son los usuarios que necesitas ganar.
En "Cómo convertirse en un hacker", Eric Raymond describe Lisp como algo así como latín o griego —un lenguaje que deberías aprender como ejercicio intelectual, aunque en realidad no lo uses:
Aprender Lisp vale la pena por la profunda experiencia de iluminación que tendrás cuando finalmente lo entiendas; esa experiencia te hará un mejor programador por el resto de tus días, incluso si nunca usas mucho Lisp en sí mismo.
Si no supiera Lisp, leer esto me haría hacer preguntas. Un lenguaje que me haría un mejor programador, si significa algo, significa un lenguaje que sería mejor para programar. Y esa es, de hecho, la implicación de lo que dice Eric.
Mientras esa idea siga flotando, creo que los hackers serán lo suficientemente receptivos a un nuevo Lisp, incluso si se llama Lisp. Pero este Lisp debe ser un lenguaje de hacker, como los Lisp clásicos de los años 70. Debe ser conciso, simple y hackeable. Y debe tener bibliotecas potentes para hacer lo que los hackers quieren hacer ahora.
En cuanto a las bibliotecas, creo que hay margen para superar a lenguajes como Perl y Python en su propio juego. Muchas de las nuevas aplicaciones que necesitarán escribirse en los próximos años serán aplicaciones basadas en servidores. No hay razón para que un nuevo Lisp no tenga bibliotecas de cadenas tan buenas como Perl, y si este nuevo Lisp también tuviera potentes bibliotecas para aplicaciones basadas en servidores, podría ser muy popular. Los hackers reales no despreciarán una nueva herramienta que les permita resolver problemas difíciles con unas pocas llamadas a bibliotecas. Recuerda, los hackers son perezosos.
Podría ser una victoria aún mayor tener soporte del núcleo del lenguaje para aplicaciones basadas en servidores. Por ejemplo, soporte explícito para programas con múltiples usuarios, o propiedad de datos a nivel de etiquetas de tipo.
Las aplicaciones basadas en servidores también nos dan la respuesta a la pregunta de qué se usará para hackear este nuevo Lisp. No haría daño hacer Lisp mejor como lenguaje de scripting para Unix. (Sería difícil hacerlo peor.) Pero creo que hay áreas donde sería más fácil superar a los lenguajes existentes. Creo que sería mejor seguir el modelo de Tcl, y proporcionar Lisp junto con un sistema completo para soportar aplicaciones basadas en servidores. Lisp es un ajuste natural para aplicaciones basadas en servidores. Las clausuras léxicas proporcionan una forma de obtener el efecto de las subrutinas cuando la interfaz de usuario es solo una serie de páginas web. Las s-expresiones se mapean bien en HTML, y las macros son buenas para generarlo. Se necesitan mejores herramientas para escribir aplicaciones basadas en servidores, y se necesita un nuevo Lisp, y ambos funcionarían muy bien juntos.
12 El lenguaje soñado
A modo de resumen, intentemos describir el lenguaje soñado del hacker. El lenguaje soñado es hermoso, limpio y conciso. Tiene un nivel superior interactivo que arranca rápido. Puedes escribir programas para resolver problemas comunes con muy poco código. Casi todo el código de cualquier programa que escribas es código específico de tu aplicación. Todo lo demás ha sido hecho por ti.
La sintaxis del lenguaje es concisa hasta el extremo. Nunca tienes que escribir un carácter innecesario, ni siquiera usar la tecla de mayúsculas mucho.
Usando grandes abstracciones puedes escribir la primera versión de un programa muy rápidamente. Más tarde, cuando quieras optimizar, hay un perfilador realmente bueno que te dice dónde enfocar tu atención. Puedes hacer que los bucles internos sean cegadoramente rápidos, incluso escribiendo bytecode en línea si es necesario.
Hay muchos buenos ejemplos de los que aprender, y el lenguaje es lo suficientemente intuitivo como para aprender a usarlo a partir de ejemplos en un par de minutos. No necesitas consultar mucho el manual. El manual es delgado, y tiene pocas advertencias y cualificaciones.
El lenguaje tiene un núcleo pequeño, y bibliotecas potentes y altamente ortogonales que están tan cuidadosamente diseñadas como el núcleo del lenguaje. Las bibliotecas funcionan bien juntas; todo en el lenguaje encaja como las partes de una cámara fina. Nada está obsoleto, o retenido por compatibilidad. El código fuente de todas las bibliotecas está fácilmente disponible. Es fácil hablar con el sistema operativo y con aplicaciones escritas en otros lenguajes.
El lenguaje está construido en capas. Las abstracciones de nivel superior se construyen de manera muy transparente a partir de abstracciones de nivel inferior, a las que puedes acceder si quieres.
Nada está oculto de ti que no tenga que estarlo absolutamente. El lenguaje ofrece abstracciones solo como una forma de ahorrarte trabajo, en lugar de como una forma de decirte qué hacer. De hecho, el lenguaje te anima a ser un participante igualitario en su diseño. Puedes cambiar todo sobre él, incluido incluso su sintaxis, y todo lo que escribas tiene, en la medida de lo posible, el mismo estatus que lo predefinido.
Notas
[1] Macros muy cercanas a la idea moderna fueron propuestas por Timothy Hart en 1964, dos años después del lanzamiento de Lisp 1.5. Lo que faltaba inicialmente eran formas de evitar la captura de variables y la evaluación múltiple; los ejemplos de Hart están sujetos a ambas.
[2] En When the Air Hits Your Brain, el neurocirujano Frank Vertosick relata una conversación en la que su residente jefe, Gary, habla sobre la diferencia entre cirujanos y médicos internistas ("pulgas"):
Gary y yo pedimos una pizza grande y encontramos una cabina libre. El jefe encendió un cigarrillo. "Mira a esas malditas pulgas, parloteando sobre alguna enfermedad que verán una vez en sus vidas. Ese es el problema con las pulgas, solo les gusta lo bizarro. Odian sus casos de pan y mantequilla. Esa es la diferencia entre nosotros y las jodidas pulgas. Verás, nosotros amamos las grandes y jugosas hernias discales lumbares, pero ellas odian la hipertensión...."
Es difícil pensar en una hernia discal lumbar como jugosa (excepto literalmente). Y sin embargo, creo que sé lo que quieren decir. A menudo he tenido un error jugoso que rastrear. A alguien que no es programador le costaría imaginar que pueda haber placer en un error. Seguramente es mejor si todo funciona. En cierto modo, lo es. Y sin embargo, innegablemente hay una satisfacción sombría en rastrear ciertos tipos de errores.