miércoles, agosto 15, 2007

La paradoja de blub

En un excelente artículo: Beating the Averages Paul Graham introduce la paradoja de Blub, un argumento con el que no puedo estar más de acuerdo.
Ciertamente los casi todos los lenguajes de programación son turing completos, así que son teóricamente equivalentes, sin embargo, intuitivamente hablando, se puede apreciar que un lenguaje como Perl5 que incluye clausuras se percibe como más poderoso que Perl4 que no las tiene.
En efecto, el lenguaje de máquina es tan poderoso como los lenguajes de alto nivel, de otra manera no se podrían hacer compiladores, y aunque se acepta que existen lenguajes de bajo nivel y lenguajes de alto nivel, no parece haber una idea clara de que hay lenguajes de más alto nivel que otros.
Para no herir los sentimientos de nadie, Paul Graham se inventa un lenguaje ficticio llamado Blub para establecer su paradoja:
Mientras el programador mira hacia los lenguajes de menor poder, sabe que está viendo hacia abajo. Los lenguajes de menor poder que Blub son obviamente menos poderosos, porque faltan capacidades a las que nuestro programador está acostumbrado. Sin embargo, cuando mira en la otra dirección, hacia lenguajes más poderosos, ni se imagina lo que está viendo. Él solo ve lenguajes extraños y probablemente los considere equivalentes en poder a Blub, pero mezclado con todas esas cosas peludas. Así que concluye que Blub es suficientemente bueno para él, porque el piensa en Blub.
Si ahora cambiamos el programador por otro que trabaje en un lenguaje más poderoso que Blub, encontraremos que cuando mire hacia abajo a Blub pensará: ¿cómo se puede hacer algo en este lenguaje? que ni siquiera tiene X.
Por inducción, los únicos programadores en capacidad de ver todas las diferencias de poder entre varios lenguajes son los que entiendan el lenguaje más poderoso.

Esta conclusión tiene profundas implicaciones en vista de que los lenguajes más populares de programación de hoy en día, y particularmente Java, son lenguajes poco poderosos, por ello podría ser difícil lograr un cambio importante en los lenguajes de programación en uso.

El lenguaje de programación Scala

Después de pensar que Java era un lenguaje sin mucho futuro, acabo de encontrar un lenguaje que podría salvar a Java y mantenerlo como el lenguaje de máquina de alto nivel de la JVM, cuyo propósito es soportar su próximo lenguaje de alto nivel.
Scala es un lenguaje de alto nivel, multiparadigmático, elegante y seguro. Soporta la programación orientada a objetos y la programación funcional y tiene una librería para soportar la concurrencia basada en el modelo de actores. Deriva de Funnel, que es más formal y me gusta un poco más.
Sin embargo Scala tiene un montón de capacidades interesantes: fue diseñado para reutilizar la plataforma existente y se integra perfectamente con Java y XML, que maneja de manera nativa con capacidades de XQuery y XPath, tiene tipos estáticos, inferencia de tipos y pattern matching.
No creo que sea el mejor lenguaje disponible, pero parece el mejor lenguaje para la JVM, y tiene una ventaja final: al ser OO podría ser adoptado rápidamente por la comunidad de Java, mientras aprenden a utilizar las capacidades adicionales que tiene el lenguaje, presentando la rara oportunidad de una transición aparentemente tranquila.
Como no lo he probado, no puedo decir que tan eficiente es, o si hay algún problema subyacente u otro tipo de detalle que podría originar problemas mayores, sin embargo, no consigo evidencia de esto en Internet.
Aún cuando la JVM no puede soportar aplicaciones masivamente concurrentes, por varias razones técnicas, los diseñadores de Scala, se las ingeniaron para aplicar control de inversión sobre el control de inversión del modelo de programación orientada a eventos para lograr un modelo de microthreads, similar al de Erlang.
Sería excelente que cesará de inmediato la gastadera de recursos en Java y se enfocaran en Scala, incluyendo la investigación y desarrollo de mecanismos en la JVM, que apoyen las capacidades de este lenguaje, permitiendo el uso eficiente de las máquinas del futuro, y abordando definitivamente el autobús al nuevo paradigma de programación.
Si se hacen los cambios apropiados a la JVM (continuaciones, variables por referencia, microthreads, etc.), Scala sería más eficiente, pero además la JVM se convertiría en una plataforma eficiente para lenguajes como Scheme, CL, ML, OCaml y Erlang, con la ventaja automática de tener tal vez el mejor JIT, del mercado que según recuerdo ahora es GPL.

lunes, agosto 13, 2007

Java descubre MAP!

Increíblemente a más de una década de su aparición se discute sobre las bondades de los iteradores internos en Java, de hecho hasta mencionan a Lisp, tal vez se están dando cuenta de que son prácticamente el único lenguaje moderno que no tiene map!
Lo que más me gustó del artículo es el último ejemplo, donde se resalta la claridad del código:
public  InternalIterable
internalize(final ExternalIterable ext) {
return new InternalIterable() {
public void iterate(Function closure) {
for (ExternalIterator it = ext.iterator();
it.hasNext(); ) {
closure.invoke(it.next());
}
}
};
}

Voy a escribirlo en Perl, que siempre se considera como un lenguaje obscuro y difícil:

sub internalize {
my $ext = shift;
return sub {
my $closure = shift;
$closure->($_) for @$ext
}
}

Serán ideas mías, pero creo que el segundo fragmento es mas claro y fácil de escribir, de leer y de entender.
De cualquier modo, ¿Será que la comunidad de Java esta comenzando a ver a los lados?
Nunca es tarde cuando la dicha es buena, y si se deciden a hacerlo, recomendaría que también copiaran algunas otras ideas de lenguajes más avanzados, particularmente en las áreas de generalización y paralelismo, si no se quieren perder el autobús.

domingo, agosto 12, 2007

El próximo autobús

En economía se suele hablar de que no existe tal cosa como un almuerzo gratis, sin embargo, en informática tenemos la empírica Ley de Moore que desde 1965 nos ha estado dando continuamente nuestro almuerzo gratis.
El constante aumento de la densidad de la electrónica logra que los procesadores sean cada vez más capaces, tengan más instrucciones especializadas y más memoria de caché integrada, mientras cada vez la memoria es más densa y barata. Por otra parte, y aunque no esta directamente relacionada con la Ley de Moore, las velocidades del reloj han seguido muy de cerca el progreso de la densidad de la electrónica, creciendo exponencialmente. Este comportamiento histórico muestra que las máquinas duplican todas sus capacidades aproximadamente cada 18 meses.
En un contraejemplo económico de almuerzo gratis los diseñadores de software evaden el tema de la eficiencia confiando en la Ley de Moore, logrando que el ineficiente diseño de hoy, será la adecuada aplicación de mañana. Un ejemplo de ello son los procesadores de palabras como Microsoft Word u OpenOffice Writer, que se sienten como un viejo Wordstar en un procesador Z80 de 8 bits a 2Mhz, con 64KB de RAM, a pesar de que ahora las máquinas tienen procesadores de 64 bits a más de 2000MHz, con 32 mil veces más RAM.
Sin embargo, hace algún tiempo que las velocidades de reloj de los procesadores no aumentan, este problema no apareció ayer, ni hace un semestre, el progreso en la velocidad de los procesadores comenzó a estancarse a finales del 2002!, como se puede apreciar en la figura. Para esa época ya existían procesadores de más 3GHz, por lo que hoy deberíamos tener procesadores de más de 14GHz, pero no es así.



Las razones de este estancamiento tecnológico son de carácter físico, y tienen que ver con la velocidad de la luz y la disipación de energía. Parece poco probable que logremos nuevas tecnologías que permitan superar las barreras encontradas. Ciertamente los fabricantes tienen en sus laboratorios procesadores de mayor velocidad, pero estos deben ser refrigerados con equipos poco prácticos para su implantación.
Debido a estos problemas, los fabricantes de procesadores se han visto forzados a aumentar las capacidades de sus productos por la vía del paralelismo. En el 2003 comenzaron los anuncios sobre múltiples unidades de procesamiento (cores) en un solo procesador, hoy los procesadores "dual core" son populares y los procesadores "quad core" están comenzando a comercializarse. Intel por su parte predice que fabricará procesadores con más de 100 "cores", y según la ley de Moore, deberían aparecer en unos 8 años suponiendo que no aparezcan nuevos problemas (se me ocurren problemas de eficiencia en el mantenimiento de la coherencia del cache).
La consecuencia inmediata de esta forma de crecimiento es: que a menos que el software sea diseñado para operar concurrentemente, no se obtendrá ningún beneficio de los nuevos procesadores. En otras palabras: se acabó el almuerzo gratis.
Algunas personas argumentarán que los sistemas operativos de hoy en día, como Linux están preparados para esto, y podremos ejecutar más procesos o hilos (threads) en nuestras máquinas, sin embargo, esta visión simplista no es completamente cierta ni siquiera en el caso de los servidores, pero está fundamentalmente errada en el caso del escritorio, donde generalmente utilizamos una aplicación al mismo tiempo.
Así como la década de los 70 fue la revolución de la programación estructurada, y la década de los 90 fue la revolución de la programación orientada a objetos, la próxima década será la revolución de la programación concurrente.
No voy a ahondar en el tema de los costos y beneficios de la programación concurrente, porque el artículo de Herb Sutter, del cual tomé la introducción, lo explica con gran detalle. Sin embargo, quiero resaltar algunos de los problemas que menciona su autor, y otros, que me ha tocado sufrir en carne propia.
  1. A los programadores les cuesta mucho más razonar sobre programas concurrentes, que sobre programas que tienen un solo flujo.
  2. Las aplicaciones no siempre son fácilmente paralelizables.
  3. Las principales técnicas que utilizamos actualmente para manejar concurrencia implican el uso de memoria compartida, cierres y semáforos, esto nos lleva irremediablemente a un universo de regiones críticas, carreras y abrazos mortales.
  4. Aún los threads nativos del sistema operativo consumen demasiados recursos y no escalan muy bien cuando la concurrencia aumenta notablemente.
Los programadores deben aprender a plantear y razonar las aplicaciones y sistemas como colecciones de hilos o procesos que llevan a cabo las funciones que los componen, de la misma manera en que antes aprendieron a utilizar los objetos. Sin embargo no creo que deban aprender los detalles acerca de los mecanismos que mencioné en el problema 3.
Evitar estos problemas requiere de programadores con conocimientos técnicos avanzados, alta capacidad de atención y mucha experiencia en el área. Mientras detectar y eliminar las carreras y escenarios de abrazos mortales en sistemas en producción es más un ejemplo de lucha contra las artes obscuras que una ciencia. Por ello es fundamental eliminar el manejo de la memoria compartida como se eliminó el manejo explícito de la memoria en los lenguajes modernos.
Para todos aquellos que crean que esto no se puede lograr, piensen en la última vez que utilizaron alguno de estos mecanismos para compartir datos o serializar el acceso y la actualización de un grupo de registros en SQL. Así que lograr abstracciones que permitan el acceso y actualización concurrente en ausencia de mecanismos de bajo nivel, es equivalente a la eliminación del goto y las variables globales, en favor de las estructuras de control y los objetos.
Algunos lenguajes ya han introducido algunos mecanismos para controlar la concurrencia; las construcciones al estilo de syncronize en Java son un ejemplo (de muy bajo nivel), lo mismo sucede con bibliotecas como ACE y Boost en C++, que ofrecen muchos mecanismos para implementar concurrencia, todos ellos de bajo nivel.
Se estarán preguntando: ¿qué es lo quiere este señor, una papita pelada?, y la respuesta es sí; pienso que se necesitan abstracciones mucho mejores, ejemplos de ellas son: la memoria transaccional, el modelo de actores, las promesas o futuros, entre otros. Todas ellas eliminan la necesidad de aprender los mecanismos de bajo nivel expresados en el problema 3.
Algunos lenguajes como E, Oz/Mozart, Io, etc. implementan el modelo de actores, sin embargo no atacan los problemas 2 y 4.
Existen lenguajes donde la concurrencia es una parte natural del planteamiento del problema, por ejemplo, en Erlang se utilizan procesos para limitar la propagación de los errores, y como efecto secundario los programas se conciben como grupos de procesos cooperativos. Este lenguaje resuelve 1, 3 y 4, y ayuda con 2. En un programa que estoy haciendo tengo unos 500 procesos, de los cuales siempre hay como 100 trabajando, así que el programa puede beneficiarse de hasta 100 procesadores. Para aquellos que piensen que 500 procesos son demasiados, recuerden que mencioné que este lenguaje resuelve el problema 4, aunque el cómo lo logra es otra historia.
Finalmente los lenguajes funcionales completamente puros como Haskell o el descrito en un artículo anterior podrían ofrecer la máxima solución. En estos lenguajes el programador no sabe el orden de evaluación, es el compilador el que elige el orden de ejecución, pudiendo paralelizar lo que considere necesario.
La investigación en torno a la concurrencia en Haskell tiene apoyo financiero y es diversa, experimentando en varias direcciones, algunas de las cuales podrían eliminar completamente todos los problemas de la lista.
La lista no termina aquí, existe toda una gama de lenguajes desde JoCaml, que utiliza sistemas formales (léase álgebras) para expresar la concurrencia a muy alto nivel hasta Cilk y Java que ofrecen capacidades de expresión de concurrencia en bajo nivel.
Sólo resta saber si las preferencias de los programadores se inclinarán a aprender los detalles de la concurrencia, o aprender nuevos lenguajes que brinden mejores niveles de abstracción del paralelismo. En mi experiencia los primeros son tan difíciles de perfeccionar que obligarán un cambio fundamental en los requerimientos del programador, así, que los lenguajes que no ofrezcan características integradas de alto nivel para el manejo de la concurrencia, no abordarán el autobús del paralelismo, y en consecuencia serán arrollados por éste.
Por supuesto que siempre está el camión de recursos que se pueden gastar en un lenguaje como Java, para agregarle caramelitos que logren mitigar un poco la falta de mecanismos serios de manejo de concurrencia, mientras los programadores ignoran el universo de posibilidades existentes en el exterior de la matriz. Con mucho menos del 10% de lo que se gastaría en esta nueva aventura, podríamos tener un lenguaje concurrente capaz de resolvernos todos los problemas mecanizables de bajo nivel, mientras los programadores se ocupan de describir procesos y algoritmos mucho más difíciles de mecanizar.

jueves, agosto 09, 2007

La tragedia de Lisp

El lenguaje de programación universal consideró: ¿por qué Lisp no surgió como alternativa superior a un montón de tecnologías?

En este artículo voy a considerar el asunto con algo más de profundidad, pues aunque creo que la razón fundamental es el mercadeo, también hablé de que Lisp tenía algunas debilidades que podrían haberse subsanado con solo el 10% de lo invertido en Java.

Aún suponiendo que Java no fue inventado, habría poca probabilidad de que Lisp se convirtiera en un lenguaje muy popular, fundamentalmente por dos razones:
  • Su naturaleza extensible
  • La comunidad detrás del lenguaje
La naturaleza extensible de Lisp, promovió la creación de múltiples dialectos del lenguaje a un punto que hoy Lisp no es solo un lenguaje, sino una familia de lenguajes que comparten la sintáxis y algo de la semántica del primer Lisp. Uno de los miembros notables de esta familia es Scheme, que cambió algunas reglas semánticas de Lisp, y fue rápidamente estandarizado por la IEEE, mientras la estandarización de Lisp en el ANSI marchaba a paso de tortuga coja.

La comunidad de Lisp, siendo mayormente académica gusta de las soluciones completas y elegantes, así que tarda mucho en ponerse de acuerdo en cual es la solución ideal para un problema, eso fue una de las causas fundamentales de la lentitud en la definición del Common Lisp (CL).

Hoy en día tenemos que las principales implementaciones de Lisp son ANSI Common Lisp (CL), que tiene un sistema avanzado de orientación a objetos llamado CLOS y Scheme que es un Lisp sencillo, pero con interesantes capacidades de crecer fácilmente en el área de la concurrencia, por la implementación de continuaciones. Ambos pueden ser compilados nativamente y a código intermedio o incluso interpretados directamente.

Scheme, tiene a su vez varios dialectos que suelen ser muy compatibles entre sí, la comunidad detrás de Scheme (ingenieros) es mucho más pragmática que la de CL (académicos), y se han mantenido extendiendo el lenguaje con regularidad mediante los "Revisedn Report on the Algorithmic Language Scheme" o (RnRS), así que las diversas implementaciones de Scheme se declaran compatibles con R4RS, R5RS, etc.

Lo cierto es que Lisp sufrió y todavía sufre de una aparente fragmentación, similar a la de Unix para los años 80, que permitió que un sistema operativo con una única implementación y semántica ganara mercado rápidamente, este fenómeno es similar con Lisp.

Sin embargo la fragmentación es aparente, podríamos considerar que CL, Scheme, QI, etc. son lenguajes diferentes, que comparten una sintaxis similar y referirnos a Lisp como familia únicamente, pero no es así, por alguna razón los vemos como un lenguaje fragmentado, de la misma manera se podría decir que C está fragmentado en C, C++, Java, ECMAscript y hasta Perl.

Me gustaría investigar un poco más, las causas que hacen que los Lisps sean considerados como dialectos, aún cuando lenguajes como QI son tan diferentes a CL, mientras los lenguajes que comparten mucho de la sintaxis y la semántica de C son considerados lenguajes diferentes.

Si a principio de los 90 Common Lisp hubiera sido una versión unificada de Lisp, habría sido mucho más fácil identificarlo como un lenguaje para la web, no solo para programar en ella, sino para definirla en base Lisp.

Si Sun, en vez de inventar un lenguaje nuevo, hubiera tomado CL como base y lo hubiera llamado Java, hoy Java sería igual de popular, pero eligieron mantenerse en lo comercial, eligieron mantener no solo la sintaxis de C sino las características de C y C++.

Algunos aseguran que Sun tenía miedo de que el lenguaje fuera demasiado avanzado (¿academico?), y puede ser, porque GJ (un java con programación genérica), apareció hace como 15 años, y no fue hasta hace muy poco que Java adquirío solo algunas características de GJ, y todavía discuten si agregan construcciones funcionales al lenguaje, fundamentalmente porque viene un autobús que no se quieren perder y Microsoft ya lo está abordando.

Sin embargo, Lisp sigue allí latente y evolucionando, esperando por la bala de plata que lo ponga a competir con los grandes, claro que ahora lenguajes como SML, OCaml, Erlang, Haskell y Clean derivados de los conceptos de programación funcional de Lisp, se van a montar en el mismo autobus que está a punto de arrancar y que dejará atrás a muchos de los lenguajes imperativos que conocemos hoy en dia (incluso a Java, si Sun no se pone las pilas).

La historia del autobus, se las debo, porque quiero escribirla con más calma.

domingo, agosto 05, 2007

El patito feo

Esta historia comienza durante una reunión de coordinación para determinar las herramientas y de desarrollo para un proyecto de una casa de bolsa.
Tierra adentro, en la parte baja de la pradera, escondido entre los altos juncos que crecían en el borde de la laguna, había un nido lleno de huevos. Mamá Pata estaba suavemente sentada sobre ellos, para darles calor. Esperaba con paciencia el nacimiento de sus patitos.
Durante una discusión sobre las características del proyecto, queda claro que el mismo deberá ser altamente concurrente y distribuido, entre otras para permitir la escalabilidad y la tolerancia a fallas. En este escenario surge inmediatamente una candente discusión sobre el lenguaje a utilizar para el desarrollo del sistema.

Jorge explica: que en el proyecto anterior la mayoría de las penurias durante el desarrollo se relacionaron con la concurrencia, las carreras por por recursos compartidos aparecían en todas partes y eran muy difíciles de depurar, el sistema ya en producción falla de vez en cuando de forma inexplicable y es prácticamente imposible reproducir los problemas. Durante este tiempo ha estado investigando el tema y propone JoCaml para el desarrollo del sistema.

Todos los miembros del equipo quedan estupefactos, y se hizo un silencio sepulcral.
Crac! Crac! Uno tras otro comenzaron a abrirse los huevos, y los patitos asomaban por ellos sus cabecitas. Pero... que será esa horrible ave gris que aparecía? Mamá Pata no salía de su asombro. "Ninguno de los otros patitos es como este!", exclamó.
Al salir de su asombro, vino la inevitable pregunta: ¿Jorge, que es eso?, es un lenguaje funcional especializado en la expresión de concurrencia y ha sido utilizado exitosamente en algunos proyectos ...

¿Y como es?, interrumpió José, mientras el resto de los integrantes introducían sendas consultas en Google. Bueno JoCaml es un lenguaje funcional, los programas se expresan siempre en base a funciones, no hay variables globales, asignaciones o ciclos, todo se logra utilizando recursión, procesos y primitivas de comunicación entre estos.
... después fueron al corral de los patos. Los otros patos los miraron con impertinencia y dijeron: "Miren, aquí viene otra cría, como si ya no fuéramos bastantes! Y qué feo es ese patito! Sáquenlo de este corral! No lo queremos!".
¿Sin variables globales, ni asignación, ni ciclos?, ¿cómo se puede hacer algo en ese lenguaje?, es absurdo, además ¿cómo cooperan los procesos si no hay memoria compartida?

En ese momento el gerente interrumpe: eso es raro, debemos entrenar a los programadores, probablemente no haya soporte, ya es tarde, y todavía debemos elegir entre varios lenguajes y docenas de bibliotecas.
Uno por uno, los patos se lanzaron sobre el patito feo y lo picotearon en el cuello, y lo empujaron de un lado a otro. Vinieron después algunos pollitos y ellos también picotearon al patito.
Francisco: ¡sí! ¿que vamos a hacer si nos enredamos y no tenemos soporte?

Jorge piensa: ¿tal vez investigar o pensar?, ¿para que tenemos Internet?

José: tal vez deberíamos analizar un poco de que se trata JoCaml y la programación funcional antes de seguir criticando ¿no creen?.

Pablo: ¡no! aquí dice que los lenguajes funcionales son únicamente de interés académico, no podemos darnos el lujo de utilizar herramientas académicas.

Juan: Si, debemos utilizar un lenguaje empresarial.

Pedro: debemos estar conscientes de que el estándar de facto en Wall Street es Java.

Luis: por cierto que algo tan raro como JoCaml debe ser para gurues.
Mamá Pata trató de proteger al patito feo. "Déjenlo tranquilo", pidió a las malignas aves, "él no hace daño a nadie". Pero de nada sirvió. Y hasta sus propios hermanitos empezaron a tratarlo mal.
Jorge: pero leí que una de las ventajas de la programación funcional es que los programas son mas fáciles de comprender, generalmente los programadores que trabajan con lenguajes funcionales producen código más modular, reutilizan más código y lo hacen de mejores maneras. Lo difícil de lograr en nuestro sistema es la concurrencia, y en JoCaml es casí imposible que aparezca un deadlock, no hay variables que compartir y las estructuras de datos son únicamente de lectura, y la concurrencia se expresa en base un álgebra evitando al programador el trabajo de secuenciar el acceso a los datos.

Pedro: un googlefight entre Java y JoCaml me da 229 millones contra 91 mil, ¡nadie usa eso!.

José: caramba, ¿álgebra?, ¿estructuras de datos inmodificables?, mejor lo dejamos así.

El gerente: si, dejemos eso de lado, evitemos los problemas, y vamos a concentrarnos en cosas útiles, deberíamos usar Java con alguna biblioteca de concurrencia y comunicaciones, de cualquier modo en Java tenemos funciones y todavía podemos utilizar todo lo demás, no creo que los programadores puedan trabajar con esas cosas raras que son de interés puramente académicas.
Todos los días era lo mismo. El patito feo no podía escapar al maltrato. "Creo que será mejor que me vaya lejos, muy lejos", se dijo por fin. Así es que, saltando el cerco, salió a viajar tan rápido como pudo.
Jorge se quedo sin argumentos y se resignó, atendiendo al resto de la reunión sin demasiado interés.

Este es el escenario típico de introducción de una "nueva" tecnología en una organización, los argumentos de descalificación son la regla, y la mayoría de ellos son FUD y es una lástima de que Jorge se haya quedado sin argumentos, tal vez si hubiera tenido algo más de experiencia con lenguajes funcionales, se habría dado cuenta de que todas las personas en la reunión y los programadores, probablemente ya han utilizado un lenguaje funcional de consultas a bases de datos llamado SQL, pero además las secretarias, su mamá, ustedes que están leyendo esto, y una cantidad importante de todos los usuarios de computadoras en el mundo, ya usan otro lenguaje funcional con increíble naturalidad y frecuencia.

Sin embargo, después de los 10 minutos de aprendizaje de este lenguaje, nadie parece notar que está programando, la mayoría ni siquiera saben que están describiendo funciones, así de intuitiva puede llegar a ser la programación funcional.

Esto se debe a que desde kinder nos están enseñando los mecanismos fundamentales de este tipo de programación, no se a ustedes, pero a mi, en kinder, me enseñaron al menos la función sucesor: suc no hay naranja = 1 naranja, suc 1 naranja = 2 naranjas, suc 2 = 3, etc., así que además ya nos comenzaban a dar inducción, esto fue muy útil cuando nos enseñaron la suma, ¿recuerdan?, tal vez no la llamaban "suc", en mi caso la llamaban "y una más" y se utilizaba notación postfija en vez de prefija: 2 naranjas "y una más" = 3 naranjas. Ahora si recuerdan.

Uno de los conceptos brillantes de éste lenguaje es que el usuario diagrama sus funciones en dos dimensiones (aunque últimamente se permite diagramar en tres) de este modo automáticamente se puede hacer un programa para lograr un cálculo y un reporte simultáneamente, pero lo hace sin utilizar asignaciones, secuencias, posiciones o comandos de impresión, solo se utilizan funciones matemáticas, diagramación y formatos.

A estas alturas deben estar pensando que no les digo como se llama el lenguaje para mantener el suspenso, pero no es así, lo que sucede es que el lenguaje del cual estoy hablando ¡no tiene nombre!, por ello somos más inconscientes de su existencia, evitando que se forme un culto a su alrededor a pesar de su importancia para la computación personal.

En 1979 un producto incluyó este lenguaje y revolucionó el uso de las computadoras hogareñas cambiando su historia y su denominación, que en adelante serían conocidas como computadoras personales, las primeras servían fundamentalmente para la recreación, mientras las últimas eran, y son, herramientas de trabajo.

El producto en cuestión si tenía un nombre: VisiCalc, y sus ideas fundamentales fueron rápidamente copiadas y mejoradas por SuperCalc, Multiplan y Lotus 123, hasta llegar hoy a productos sofisticados como Microsoft Excel y OpenOffice Calc, mientras la gran mayoría de los usuarios, siguen utilizando únicamente su característica fundamental: la descripción de un modelo matemático en base a funciones dentro de una cajita, que se componen con más funciones que están en otras cajitas dispersas a través de la hoja electrónica.

Esta sinfonía matemática donde los resultados se propagan automáticamente desde los datos iniciales a sus respectivas cajitas, es el máximo ejemplo de programación funcional descriptiva, el usuario no se preocupa por la secuencia de ejecución, no necesita las asignaciones, solo trabaja describiendo composiciones de funciones y es un ejemplo del último objetivo de la informática: describir un problema y que nos den la solución.

Los "expertos" rápidamente acotarán que este lenguaje no es turing completo, que no sirve, etc. sin embargo no estoy diciendo que sirve para cualquier cosa, estoy diciendo que es el lenguaje de programación más popular que existe sobre la tierra, y que demuestra que la programación funcional y declarativa es mas natural e intuitiva que la programación imperativa, a pesar de que durante los años de aprendizaje en informática, nos hayan hecho olvidar lo que aprendimos desde kinder.

El caso del lenguaje utilizado por las hojas de cálculo es extremo, pero, ¿ si existen lenguajes capaces de ayudar en esta dirección, no vale el dicho popular: "agarrando aunque sea fallo" ?, pienso que hay muchos casos en los cuales la respuesta es sí, incluso sopesando los problemas asociados como la presunta falta de soporte, herramientas y bibliotecas.
"Me gustaría ir con ellos", se dijo el patito. Quizá ni siquiera me hagan caso, por ser tan feo. Pero, sin embargo, no importa, lo intentaré".
Voló hasta el agua y nadó rápidamente hacia ellos. Pero cuando miró hacia abajo y vio su propio reflejo en el agua clara, que sorpresa! Ya no era un ave gris y fea, como le había parecido siempre. Él también era un hermoso cisne blanco.
Sucede con frecuencia que las cosas más obvias son las menos evidentes (como la manzana de Newton), y sin un análisis serio sobre tecnologías diferentes a las que nos venden, o las que usa "todo el mundo", estamos limitando artificialmente nuestra visión, y como consecuencia llegando a conclusiones que pudieran ser diametralmente opuestas a la verdad.

Así es como descartamos de plano algunas cosas porque no coinciden con nuestros parámetros de evaluación, pero si nunca nos salimos de la caja, ¿cómo nos vamos a dar cuenta de que ni siquiera estamos usando los parámetros adecuados para evaluar algo de una especie diferente?

sábado, agosto 04, 2007

El lenguaje de programación universal

El mundo de la informática se ha vuelto sumamente complejo hoy en día, cientos de librerías, docenas de lenguajes de programación, estas variables hacen del desarrollo de software una pesadilla. sería muy interesante tener un único (o al menos principal) medio de expresión del software, que la gran mayoría aceptara como la manera de comunicarse con las máquinas.

Vamos a explorar una de las maneras de atacar el problema de la complejidad del software, intentaremos crear un lenguaje ideal, el lenguaje de nuestros sueños, que ofrezca todos los mecanismos que nos permitan expresarnos de la manera que mejor nos parezca para resolver un problema dado, un lenguaje que soporte todos los paradigmas actuales de la programación. Ante todo, nuestro lenguaje debe soportar la programación estructurada, la programación orientada a objetos (OOP) en todas sus variantes, la programación funcional y la programación concurrente.

Deberíamos ser incluso más generales, diseñaremos un lenguaje sencillo pero extensible, así, podríamos partir de una base extremadamente simple, que nos permita implementar nuevas funcionalidades o extensiones a partir de la misma, así además de lograr implementar los paradigmas actuales de la programación, tendremos una muy buena oportunidad de implementar los paradigmas que se inventen en el futuro, cuando estos aparezcan.

Como nuestro lenguaje pretende ser extensible lo llamaremos LPX por las siglas de Lenguaje de Programación eXtensible, que no es muy original pero captura la idea.

Queremos que LPX no solo sea una moda, que sea la sólida base para una plataforma que dure al menos hasta que las máquinas adquieran la suficiente inteligencia para describirles los problemas y que ellas mismas nos den la solución.

Deseamos que LPX sea adoptado ampliamente y por ello debemos diseñarlo para que sea fácil de aprender y muy fácil de leer incluso para personas sin nociones de programación, permitiendo a los usuarios con un mínimo entrenamiento hacer modificaciones sencillas a programas existentes. Tal vez esto parezca una utopía, sin embargo, son los usuarios prácticamente sin entrenamiento, los que universalmente extienden Excel haciendo macros en Visual Basic para Aplicaciones (VBA), LPX debería ser más fácil de aprender que VBA.

LPX será mejor si tiene pocos elementos de sintaxis, evitaremos la mayor cantidad posible de signos de puntuación, palabras reservadas, y reglas de construcción de oraciones. Por ello quedan descartados rápidamente lenguajes como Perl (¡que lastima!) y C++, mientras que lenguajes como Ruby, Java y Pascal podrían ser opciones viables, aunque intentaremos que sea aún mas simple.

Un lenguaje que se simplifica la sintaxis al máximo es Python, y de hecho podría servirnos, si no fuera por su esquema de sintaxis diagramada, donde los espacios en blanco especifican la estructura del programa, es antinatural para casi cualquier persona, además complica las herramientas, pues hasta el compilador tiene problemas con esto, así que lo descartaremos.

Copiar una sintaxis preexistente es lo más fácil cuando se quiere diseñar un nuevo lenguaje, porque se puede reutilizar el código de las herramientas existentes y hasta parte de la plataforma operacional; además es importante que nuestro lenguaje sea fácil de manipular para facilitar el desarrollo de herramientas como IDEs y generadores de código que son un factor importante en la adopción del lenguaje.

Aunque pareciera que nos estamos quedando sin opciones, hay todavía una sintaxis sencilla, que tiene poquísimas reglas de construcción, es fácil de manipular automáticamente, está ampliamente difundida y es extensible: ¡ XML !. Si además evitamos los atributos en las marcas de XML y algunas otras características exóticas, su aprendizaje se reduce a saber que una marca se arma con <nombre>contenido</nombre> como ¡ única regla de sintaxis !.

Debo desviarme un instante de la discusión para advertir que estoy muy consciente de que hay mucho más que sintaxis en la barrera de adopción de un lenguaje, la semántica (el significado) también es importante, y por ello LPX tendrá las capacidades de cualquier lenguaje procedimental (asignación y secuencia) y también tendrá funciones de orden superior (higher order functions), que es solo un nombre rimbombante para las funciones que reciben, manipulan o retornan otras funciones, por ejemplo en Perl (hubiera preferido poner un ejemplo en Java, pero no tiene esta capacidad, afortunadamente hay lenguajes más útiles):

sub fold { my ($func, $valor, $lista) = @_;
for my $elemento ( @$lista ) {
$valor = $func->($elemento, $valor)
}
$valor;
}

my $sumar = sub { my ($a,$b) = @_; $a + $b };
my $multiplicar = sub { my ($a,$b) = @_; $a * $b };

print fold($sumar, 0, [1, 2, 3, 4, 5]), "\n";
print fold($multiplicar, 1, [1, 2, 3, 4, 5]), "\n";

La función fold recibe una función, un valor inicial y una lista para aplicar la función a todos los elementos de la lista para lograr una acumulación, así la última línea del ejemplo calcula el producto de los elementos en la lista y la penúltima calcula la suma.

Espero que el ejemplo deje claro que está técnica es muy útil aunque solo sea para reutilizar código, pero también ilustra porque Java es un lenguaje limitado y porque la sintaxis de Perl fue la primera en ser descartada.

Habiéndome cuidado de que me reclamen los conocedores, ahora solo debemos hacernos una idea de como luce nuestro lenguaje.

Una expresión como a+b*15 sería algo como:

<sum>
<var>a</var>
<mult>
<var>b</var>
<num>15</num>
</mult>
</sum>

Una llamada a una función:
<factorial>
<param><num>10</num></param>
</factorial>

Ummm??, me da la impresión que el lenguaje es fácil de entender, sin embargo, está algo sobrecargado, además estamos introduciendo muchas palabras, vamos a hacer la función factorial para ver cómo luce:

<define>
<func>factorial</func>
<param>n</param>
<body>
<if>
<cond><lt><var>n</var><num>2<num></lt></cond>
<then>
<return><num>1</num></return>
</then>
<else>
<return>
<mult><var>n</var>
<factorial>
<subst><var>n</var><num>1<num></subst>
</factorial>
</mult>
</return>
</else>
</if>
</body>
</define>

Definitivamente sufrimos del problema de siempre en XML, las marcas terminan resaltando mucho más que los datos (en este caso, el algoritmo). Podríamos remediar parte del problema si elimináramos algunas de las marcas complicando algo nuestro compilador para reconocer números, nombres y algunas secuencias de construcciones separadas por espacios en blanco. Así una expresión como:

<lt><var>n</var><num>2</num></lt>

se podría simplificar a:

<lt>n 2</lt>

Sin embargo nos interesa mantener esta última estructura, pues de este modo <lt> puede ser un operador incluido en el lenguaje o una llamada a una función como en: <factorial>n</factorial>, lo que nos permitirá extender el lenguaje ad infinitum, agregando funciones. También eliminaremos la necesidad de especificar el valor de retorno y asumiremos que el último valor evaluado por una función es el valor de retorno (tenia que dejar algo de Perl). Veamos como luce ahora LPX:

<define>
<func>factorial n</func>
<body>
<if>
<cond><lt>n 2</lt></cond>
<then>1</then>
<else>
<mult><var>n</var>
<factorial>
<subst>n 1</subst>
</factorial>
</mult>
</else>
</if>
</body>
</define>

Con estos cambios hemos perdido algo de la flexibilidad de XML, y la posibilidad de procesar todos los aspectos de nuestro programa con las herramientas existentes. Sin embargo, todavía LPX es fácil de procesar automáticamente, aunque desafortunadamente no tan fácil de aprender y menos de leer. Entre otras, no hemos eliminado todas las secuencias. Por ejemplo si asumimos que <if> siempre tiene tres elementos (cond, then, else) separados por blancos, podríamos simplificar aún más el lenguaje a costa de introducir secuencias.

Una vez comprobado que perder algunos beneficios puede hacer LPX más transparente. Podemos transformar las marcas de XML, para escribirlas de forma diferente. Así, en vez de escribir <marca>contenido</marca>, escribiremos <marca contenido>, de esta manera se mantienen las propiedades de facilidad de procesamiento y la claridad aumenta notablemente:

<define <factorial n>
<if <lt n 2>
1
<mult n <factorial <subst n 1>>>
>
>

Ahora sí estamos avanzando, se ve sencillo, fácil de aprender y de leer, sin embargo, sería interesante poder utilizar marcas que no fueran nombres, sino símbolos. Esto permitiría escribir:

<mult n <factorial <subst n 1>>>

de una manera más natural:

<* n <factorial <- n 1>>>

Claro que ya no sería tan fácil con <lt n 2> pues <<n 2> se prestaría a confusión, pero esto se arregla fácilmente reemplazando '<' y '>' por '{' y '}', quedando así:

{define {factorial n}
{if {< n 2}
1
{* n {factorial {- n 1}}}
}
}

Perfecto, fácil de entender, fácil de aprender, fácil de extender infinitamente y fácil de manipular. Ahora solo tenemos que ponernos a trabajar en un compilador, un interpretador y comenzar a usarlo en todos partes.

Pero no nos apresuremos, se me ocurre una idea para facilitarnos el trabajo (luego les digo porque), en vez de usar '{' y '}' podríamos utilizar '(' y ')':

(define (factorial n)
(if (< n 2)
1
(* n (factorial (- n 1)))
)
)

Creo que me gustaba más como se veía anteriormente, aunque esto puede ser simplemente porque antes se parecía más a Perl (a estas alturas se deben imaginar que me gusta Perl). Pero, como dije anteriormente, el cambio no fue un capricho, estaba esperando reutilizar algunas herramientas ya existentes.

En efecto, ¡ ya ese lenguaje existe !

Bienvenidos a Lisp, un lenguaje sencillo, fácil de aprender y manipular automáticamente, creado en 1958. Tiene hoy la misma vigencia de siempre, la forma de su sintaxis ha permitido el desarrollo de herramientas de procesamiento que le dan la capacidad de extenderse a sí mismo, y adaptarse a cualquier paradigma inventado o por inventar.

Caracterizado como ``el lenguaje de programación programable'', Lisp ha sido capaz de adaptarse a nuevos paradigmas de programación, debido a su capacidad de extenderse a sí mismo, así es como hoy en día maneja objetos mejor que casi cualquier otro incluidos Java y C++, aunque la aparición de la programación orientada a objetos sucede casi tres décadas después de la invención de Lisp.

En Lisp se utilizaba la orientación a aspectos utilizando funciones de orden superior antes de que se inventara la programación estructurada, pero además las funciones de orden superior logran que ese concepto sea tan natural, que ningún programador de Lisp puede entender como alguien puede ser capaz de soportar cosas como AspectJ.

En Lisp se hacían programas genéricos décadas antes de que la programación genérica se pusiera de moda. Las plantillas de C++ palidecen con respecto al sistema de metaprogramación (macros) de Lisp, que permite extender el lenguaje prácticamente sin ningún límite, y los macros ya eran populares para cuando se conciben las plantillas de C++.

Lisp puede manipular listas de elementos de forma simple, efectiva y eficiente, en efecto su nombre viene de LISt Processor, y las listas en Lisp se representan como elementos encerrados entre paréntesis:

(1 2 3 esta es una lista en lisp 4 5 6)

Un programa en Lisp, es también una lista de Lisp, así que procesarse a si mismo es muy natural, una característica que que ha sido mucho valor en el campo de la inteligencia artificial.

Lisp se puede compilar eficientemente a lenguaje de máquina, logrando un rendimiento y tamaño similar al de programas equivalentes de lenguaje C, pero es muy fácil de interpretar con una sencilla máquina virtual, que podría funcionar con holgura en cualquier reloj de pulsera.

Lisp permite implementar concurrencia eficiente y segura con mínimas modificaciones al lenguaje (vean Termite, o mejor aún, pruebenlo).

A finales de los 80 cuando la gente se da cuenta de que los punteros son malos, en vez de mirar a los lados, o incluso hacia atrás, se ponen a hacer algo completamente nuevo, el gran drama del ``no se inventó aquí'', así nace Java, un lenguaje moderno cuyas características más resaltantes para la época eran:

  1. Liberar al programador del manejo explicito de la memoria y

  2. La independencia de la plataforma

Dios mio! Lisp ya tenía 25 años con esas características cuando los diseñadores de Sun comenzaron a concebir Java.

Cuando Netscape decide agregar programas a las páginas web desarrollaron Javascript, ignorando que en esa época, el lenguaje para procesar HTML era DSSSL, que es prácticamente un subconjunto de Lisp.

La mayoría de la gente opina que XML ha sido un gran avance para la informática porque permite la expresión de cualquier jerarquía de datos con mucha facilidad y flexibilidad, estas ventajas evidentes solo son comparables con la ofuscación que le produce a los datos mismos, ¿recuerdan nuestro primer intento de una sintaxis para LPX?, sin embargo XML y las listas de Lisp son isomorfas (una palabrota para decir equivalentes), pero con una sintaxis mucho más sencilla. Si todo el sistema de XML se hubiera basado en las expresiones S (las listas de Lisp), la mayoría de las herramientas hubieran estado listas antes de comenzar el diseño.

No puedo imaginar la cantidad de enredos, dificultades e ineficiencias que ha provocado la combinación XML/javascipt, sobre todo cuando ya existía una plataforma, simple y eficiente para manejar todo.

En publicaciones recientes como CREST (Computational REST), los investigadores "descubren" que los sistemas distribuidos se pueden ver como una sola aplicación donde el estado de la misma se distribuye mediante continuaciones, mientras hay sistemas en Lisp que llevan años utilizando esos mecanismos.

Durante el desarrollo de las bases de datos relacionales, se inventó SQL (en 1974) como lenguaje fácil para efectuar consultas, pero tomando en cuenta que una entidad (tabla) no es más que una lista de tuplas (registros) que a su vez son listas de elementos (campos), deberíamos suponer que Lisp hubiera sido ideal para hacer el trabajo, pues si lo era, y todavía lo es, pero de haberlo usado como lenguaje de consultas, hoy utilizaríamos el mismo lenguaje para consultar bases de datos relacionales, bases de datos jerárquicas y bases de datos orientadas a objetos con la misma interfaz, da tristeza lo que pudo haber sido y no fue.

Aún hoy se discute como agregar clausuras, y concurrencia eficiente y segura en Java, cuando Lisp tenía estas capacidades para el momento en que Java estaba siendo diseñado.

Y entonces, ¿por qué la gran mayoría de los programadores usa C++, Java o lenguajes similares aunque probablemente mejores?, la verdad es que solo puedo pensar en tres razones:
  1. Ignorancia: aunque me parece poco creíble en vista de la amplitud y capacidad técnica de los integrantes de los equipos que han diseñado todo esto.

  2. Falta de visión: Lisp tenía y tiene algunas carencias que probablemente influyeron en la decisión de ignorarlo, sin embargo, nadie se imaginó que con solo el 10% de los recursos destinados a hacer cosas nuevas se podrían haber subsanado, en efecto si el 10% de los recursos utilizados en el desarrollo de estupideces en Java, se hubieran invertido en Lisp, hoy sería un lenguaje universal y la informática sería mucho más fácil para todos y para todo.

  3. Mercadeo: es decir propaganda, difusión de prejuicios y stereotipos que pudieron influenciar a los diseñadores para lograr tecnologías adaptadas a los productos de las casas de software, y a los usuarios para que adoptaran estas tecnologías.

Si como pienso, el mercadeo fue la causa fundamental que nos mantiene en este embrollo interminable de lenguajes y herramientas, estaremos condenados por siempre a este purgatorio, donde la propaganda opaca y hasta desaparece cualquier cosa que sea una posible amenaza para las grandes casas de software que no cesan en su intento de monopolizar las tecnologías de información.

Es increíble que como sociedad simplemente aceptemos lo que nos imponen, sin reflexionar sobre las consecuencias, lo mismo nos sucede con Windows (aunque esa es otra historia), y es la confirmación más evidente de la tendencia a la imitación que tenemos los humanos, y de que millones de moscas si pueden estar equivocadas.

Yo por mi parte prefiero ampliamente el filete a la bosta, ¿y ustedes?.


Recursos:
Si están interesados en aprender Lisp, les recomiendo comiencen con Scheme utilizando el libro How to design programs y como ambiente de aprendizaje DrScheme, si son afortunados usuarios de Debian solo deben instalar el paquete drscheme.