El proyecto que me dio una visión más profunda de la programación, y cambió mi filosofía de programación al núcleo, fue diseñar un lenguaje de programación .
La palabra clave es diseñar : no se trataba de implementar un lenguaje existente y aprender sobre compiladores; Si bien es importante, ese tipo de cosas no es fundamental. No, este proyecto en realidad estaba diseñando mi propio lenguaje desde cero.
Mi principal conclusión fue que la semántica es importante . Muy importante. ¿Todas esas cosas que aprendiste sobre la semántica operacional y la semántica denotacional y el cálculo λ y la teoría de tipos? Son cómo deben diseñarse los idiomas . Ahora no tengo el tiempo ni la paciencia para lidiar con idiomas que fueron pirateados sin pensar profundamente en cuestiones fundamentales. Ya sabes, la mayoría de ellos.
- ¿Qué framework PHP debería aprender en 2017?
- ¿Cuál es el mejor para hacer un sitio de nicho: WordPress o Joomla?
- ¿Qué es la optimización de motores de búsqueda (SEO) y por qué mi sitio web necesita SEO?
- Cómo crear un cronómetro con una vista frontal con el nodo JS
- Si Microsoft hizo ASP.NET de forma gratuita como PHP, ¿se haría cargo de PHP o no?
La semántica es lo que busco en un idioma . No tienen que estar completamente formalizados o especificados; Solo me importa que los diseñadores de idiomas piensen realmente en ellos. Puntos de bonificación importantes para la semántica denotacional limpia (o al menos limpia). Me imagino que lo que significa mi programa es lo más importante : cómo se comporta debería ser una función de su significado, no viceversa. Esto hace que los programas sean más fáciles de escribir, más fáciles de pensar y más fáciles de mantener.
Aprendí estas cosas diseñando un lenguaje sin pensar mucho en la semántica en ningún sentido formal y viendo lo fácil que es conseguir un desastre completo. Nunca usaría el lenguaje que diseñé para algo serio, ¡pero no es tan diferente de JavaScript o PHP o incluso Python!
Diseñando un lenguaje
Dejando a un lado todas esas lecciones, lo primero que aprendí fue que realmente no sé cómo nombrar las cosas. ¡Terminé llamando a mi lenguaje TPL para “Lenguaje de programación de Tikhon”! Tan creativo. Una pena que el mismo esquema realmente no funcione cuando finalmente escribo un compilador Haskell.
Bien, lo admito, esa lección no es muy profunda.
Las ideas profundas llegaron con el diseño de cómo el lenguaje debería funcionar desde cero. No tanto diseñar la sintaxis que terminó siendo muy simple y minimalista sino diseñar la semántica .
Mi enfoque para armar el lenguaje fue muy ad-hoc. Tenía un intérprete y, cada vez que pensaba en una función, simplemente la implementaba. Luego jugaba con él en el REPL o en los archivos fuente para ver si me gustaba. La mayoría de las características que me gustaron. Algunos necesitaban ajustes. Un poco realmente no funcionó en absoluto, no importa lo que intenté.
Fuera de las características que realmente no funcionaron, creo que la más importante fue la coerción automática entre tipos a la JavaScript. Cuando comencé el lenguaje, quería hacer esto bien : sistemática, extensible e inteligente. Resulta que simplemente no puedes. Es un diseño terrible. Va a hacer un lío con su código sin importar lo que haga. Originalmente, estaba realmente muy emocionado por hacer esto, pero terminé eliminándolo completamente de mi idioma.
Accidentes felices
Otras veces, características interesantes surgieron por accidente. Mi ejemplo favorito sería mi enfoque de “pereza a pedido” ¹, que le permite diferir fácilmente los valores que se ejecutarán más adelante. Básicamente, puede “diferir” una expresión como x + 10
con $
. Esta expresión se vuelve a evaluar cada vez que se usa, lo que le permite responder a los cambios en las variables que usa:
y = $ (x + 10) - x = 0 e y 10 x <- 10 - ahora x = 10 y - ahora y se vuelve a evaluar como 20 ya que x cambió
Esto resultó ser muy útil en muchos casos. ¡Pero el núcleo de la idea fue en realidad el resultado de no pensar en mi diseño originalmente! En particular, utilicé el estilo Haskell para la aplicación de funciones, donde las funciones siempre tienen currículum y no usas paréntesis ni comas. En particular:
f (a, b + c, d) - estilo C fa (b + c) d - en TPL (y Haskell y ML y ...)
Por supuesto, Haskell hace esto realmente bien porque las funciones con 0 argumentos no existen; la idea ni siquiera tiene sentido. ¡Pero en mi lenguaje imperativo, las funciones de 0 argumentos son realmente útiles! Entonces, ¿cómo puedo saber si f
es una llamada a función sin argumentos o una referencia a la función en sí?
OCaml hace esto obligando a todas las funciones como esta a tomar un argumento ficticio ()
(pronunciado “unidad”). Pero siempre pensé que esto era estúpido. (¡Apestas OCaml!) Así que decidí que f
fuera una llamada de función si f
era una función de 0 argumentos. Y para referirme a f
sin llamarlo, agregué $
como sintaxis. Luego, esto terminó siendo útil para diferir cosas que no eran funciones, y se convirtió en una característica genial que terminé extendiendo un poco.
Minimalismo
Una cosa interesante que encontré fue que la mayoría de los idiomas tienen más conceptos de los que necesitan. Me suscribí a una estética relativamente minimalista (tanto en programación como en todo, desde diseño gráfico hasta música), así que realmente no me gustó esto. En mi idioma, terminé tratando de colapsar tantos conceptos diferentes en uno como sea posible.
Un ejemplo concreto serían los objetos. Primero, tomé el enfoque JavaScript / Lua de hacer objetos hash-maps (y viceversa). Esto estuvo muy bien. ¡Pero luego noté que mi implementación de ámbitos era casi la misma que mis objetos! Así que terminé haciendo ámbitos ciudadanos de primera clase: puede especificar un objeto arbitrario como el alcance de una función, y puede acceder al alcance actual como un objeto.
Esto terminó siendo muy poderoso cuando extendí objetos con la posibilidad de personalizar cómo se realizan las búsquedas (similar a los proxies de JavaScript). Esto me permite hacer objetos personalizados con diferentes tipos de comportamientos de búsqueda y usarlos como ámbitos para travesuras de estilo que faltan en el método Ruby. Un gran caso de uso sería el script de shell, donde podría tener un objeto que busque variables desconocidas en su ruta para permitirle usar binarios directamente en el programa. ¡Podría tener esto como una biblioteca pura, sin tener que agregar capacidades de secuencias de comandos al lenguaje central!
Esta experiencia me convenció de que la mayoría de los idiomas están sobre diseñados . Ahora soy totalmente partidario de la idea de que los idiomas deberían poder crecer . Esto se resume mejor en la maravillosa charla de Guy Steele “Growing a Language” ², que sigue siendo mi charla CS favorita. Más simple es mejor .
Loco
Ahora, cuando estaba escribiendo mi idioma, mi intérprete ciertamente tenía errores. Algunos de ellos eran muy difíciles de cazar. Multa. Pero la mayoría de los proyectos de programación tienen errores. Y los más duraderos tendrán errores duros. Sí, mejoré en la depuración, pero eso no dice mucho.
No, los errores interesantes no estaban en el intérprete. Estaban en el diseño del lenguaje en sí ! Es decir, mis problemas principales surgieron cuando el intérprete realmente funcionó perfectamente bien. Es la semántica del lenguaje la que terminó siendo inconsistente. Y dado que no tenía un modelo simple para mi semántica, era casi imposible de depurar o arreglar sin reestructurar profundamente el lenguaje. Es por eso que me he vuelto tan adherente a tener una semántica explícita o al menos operativa para su lenguaje: la semántica formal es el modelo simple que le permite depurar el diseño de su lenguaje . ¿Y le gustaría usar algo que no podría haberse depurado correctamente? ¡Probablemente no!
Después de diseñar esto, leí un libro interesante sobre el diseño de Bondi, un lenguaje funcional con patrones de primera clase. Es un libro bastante bueno y un lenguaje interesante. Pero lo principal que aprendí fue la forma en que se presenta el lenguaje: se desarrolla extendiendo iterativamente el cálculo λ sin tipo con características adicionales. Esto hace que sea muy fácil considerar las características por sí mismas y ver el “núcleo” del lenguaje, la parte que realmente importa. A su vez, esto asegura que cada característica tenga sentido y hace que las esquinas extrañas en el diseño sean mucho menos probables.
Básicamente, esto lleva las ideas de modularidad de la programación normal al diseño del lenguaje, con las mismas ventajas. Es mucho más fácil trabajar en piezas pequeñas a la vez, con una complejidad incidental mucho menor de la que preocuparse.
La próxima vez que esté diseñando un lenguaje, este es el enfoque que adoptaré.
Y esto es también lo que espero de los idiomas que otras personas diseñan. Al igual que me alejaría de los programas llenos de código PHP de spaghetti, compuestos por vulnerabilidades de seguridad unidas a variables globales, me gustaría evitar lenguajes que no se diseñaron bien con características que simplemente no tienen sentido. Me gusta JavaScript. O PHP.
El respeto
Diseñar un idioma también tuvo el mismo efecto que aprender a cocinar. Después de poner un poco de esfuerzo en cocinar en casa, he ganado un gran respeto por los mejores chefs que hacen que las cosas que son innovadoras, interesantes y se vean maravillosas además de tener un sabor maravilloso. Del mismo modo, tengo un inmenso respeto por los diseñadores de buenos idiomas.
Ni siquiera puedo imaginar cómo hacer algo así …
Por otro lado, en realidad he perdido el respeto por muchos restaurantes de gama baja. ¿Lo haces profesionalmente pero no puedes hacer algo sustancialmente mejor que yo en casa? ¡Guauu! En cambio, intentas que tenga buen sabor con grandes cantidades de sal, grasa y azúcar. Y estoy aún menos impresionado por la comida rápida, aunque eso es lo que sirven los restaurantes más populares.
Esto es lo que pienso de PHP ahora.
Tal vez eso me hace un poco snob, pero creo que no hay nada de malo en preocuparse por la calidad. (Y, tal vez, algo malo en no preocuparse por la calidad). Es doblemente cierto para los lenguajes de programación porque, a diferencia de la buena comida, todos los mejores lenguajes son gratuitos.
Entonces sí: TPL fue probablemente mi proyecto más formativo . No solo me enseñó más sobre programación, sino que también cambió profundamente mi perspectiva sobre mis propios programas y diseños, así como sobre otros programas y lenguajes de programación. Fue una experiencia increíblemente valiosa.
Notas al pie
Aside Como pedante aparte, estrictamente no es “pereza” porque las expresiones pueden evaluarse más de una vez. Pero ese es el nombre que elegí entonces y ese es el nombre que usaré ahora. ¡Suena mucho mejor que “call-by-name on demand”!
² Aquí está el video de YouTube. Comienza extraño, ¡pero vale la pena verlo!