sábado, 24 de octubre de 2009

Evolución de lenguajes

[English translation]
Tal vez se ha notado que no he escrito mucho últimamente, la razón fue que tuve que dedicarme por completo una migración de emergencia.
El sistema en cuestión pertenece al cliente que me contrató para desarrollar una aplicación web, que casualmente fue mi primera aplicación de este estilo allá por 1998.


En aquella época estaba dando mis primeros pasos con Perl, pero de alguna manera me convencieron de que la aplicación debía desarrollarse en PHP, poco a poco me dí cuenta que PHP no era lo suficientemente flexible y termine haciendo varios programas en Perl que implementaban la mayor parte de la funcionalidad, aún así toda la interfaz de la aplicación se quedó en PHP.

Durante este proyecto aprendí que Perl era mucho más versátil, poderoso y divertido que PHP, y si no me hubiera dejado convencer, probablemente hubiera conseguido CGI.pm, toda la aplicación estaría en Perl y no tendría una historia para contar.

Al comenzar la migración se me ocurrió que sería más fácil poner a funcionar primero la interfaz, ya que pensé que siendo la parte más simple de la aplicación podría migrarla fácilmente a PHP5, nada más alejado de la realidad, nada funcionaba como debía.

Entiendo que todo evoluciona, pero es que los programas ni siquiera compilaban, algunos APIs han cambiado lo suficiente como para necesitar una completa revisión de todo el código fuente, y por supuesto el programa era un asco, ¿que otra cosa podría ser?, en aquella época no había una librería de templates, el acceso a la base de datos se hacía con las horribles funciones de la librería de PHP3, y toda la aplicación es un monumento a la programación estilo ASP, donde la vista, el modelo y los controladores estaban completamente integrados, como si los hubieran pasado por una licuadora.

Lo cierto es que fue más fácil compilar PHP3 en el nuevo sistema operativo que intentar migrar el código a PHP5, por lo que doy gracias a Debian y particularmente a archive.debian.org, que debe ser uno de los pocos sitios en el planeta que todavía guardan las fuentes de eso (PHP3), porque la comunidad del lenguaje tiene una política de retirarlas de internet.

Una vez superado el problema (es decir PHP), tenía pesadillas sobre el montón de código Perl 5 que no funcionaría para nada en el nuevo Perl 5.10, así que comencé resignado a copiar todos los archivos.

Lo primero en fallar fue DBI, en efecto la cadena de conexión a la base de datos utilizaba un formato que ni recordaba que existió:

  DBI->connect("dbi:Pg:dbname=mydb@myhost.com", "user", "pass");
una vez que la cambié por la sintaxis actual:

  DBI->connect("dbi:Pg:dbname=mydb;host=myhost.com", "user", "pass");
Probé nuevamente el programa y todo funcionó, procedí a probar otro programa y todo funcionó, así seguí programa por programa sin encontrar ninguna falla, ni una más.

Una década de evolución del lenguaje y del CPAN y casi todo funcionó a la primera, estaba asombrado (y por supuesto complacido).

Al contrastar la evolución de Perl y PHP en el tiempo puedo apreciar que mientras el primero ha adquirido una gran cantidad de características, como nuevas construcciones del lenguaje, varios sistemas de implementación de concurrencia, desde la programación orientada a eventos hasta los threads pasando por las corrutinas, nuevas abstracciones para facilitar y mejorar la OOP que permiten al programador elegir entre varios sistemas de orientación a objetos, avances importantes en el área de meta programación, y algunos otros, el segundo básicamente ha obtenido un sistema de OOP, y aún así es más incompatible que Perl.

La pregunta es: ¿por qué esta diferencia radical?, sobre todo cuando se dice que hay muchas más soluciones listas en PHP que en Perl y se supone que debería haber un mayor desarrollo para el primero.

Puede dar la impresión de que lo que digo pretende hacer leña de PHP, pero no es así, PHP fue solo el disparador que me hizo pensar en el problema, si tomáramos otros lenguajes, probablemente llegaría a conclusiones similares, por ejemplo: ¿cuanta evolución ha tenido Python en la última década?, y ¿será que un programa de hace 10 años funciona en el ambiente actual?, ¿y en Java?. Yo apuesto a que Perl les gana a ambos en estos frentes.

Aunque Perl es una solución New Jersey, el lenguaje en sí está muy influenciado por Lisp que es una solución MIT, y aunque se puede argumentar sobre la complejidad de la sintaxis de Perl, lo cierto es que la semántica de sus operaciones es bastante consistente y sumamente versátil, además se puede extender el lenguaje con módulos, permitiendo la experimentación sin necesidad de modificar el interprete.

Otros ambientes de programación tienen herramientas muy buenas que facilitan enormemente la realización de compiladores, pero el nivel de complejidad en la implementación de alguna extensión, que en general se deben implementar como preprocesadores, limitan el desarrollo de este tipo de extensiones.

Por otra parte la falta de integración con el compilador obliga al usuario a introducir complejidad en la construcción del software, ejecución de precompiladores, manejo de archivos temporales, y otros, que se convierten en una pesadilla sobre todo en ambientes dinámicos, imaginemos que para para cargar un módulo en Moose, se necesitara ejecutar el precompilador de Moose y luego cargar el módulo resultante de la compilación, sería realmente fastidioso, sin embargo en Perl existen mecanismos que permiten que se ejecute automáticamente el compilador de Moose, que accede al código fuente e ingresa el resultado automáticamente al compilador de Perl para su fase final de compilación, y el usuario lo único que debe hacer es: use Moose; al comenzar un programa.

Los problemas anteriormente descritos limitan la adopción de extensiones externas al lenguaje, coartando la evolución por la vía de la extensión, que es la manera más fácil de evolucionar un lenguaje. En este sentido Perl se parece mucho a Lisp, y la mayor parte de la evolución del lenguaje se logra agregando módulos externos en el CPAN. Hoy en día existen extensiones que implementan diversas estructuras de control como try/catch, también hay módulos que permiten declarar los parámetros de las subrutinas y los métodos, y  muchas otras cosas que no son parte integral del lenguaje.
Con respecto a la compatibilidad, hay dos factores a tomar en cuenta: la compatibilidad de las librerías y la del lenguaje.

La compatibilidad del lenguaje se mantiene utilizando los pragmas, que en Perl son prácticamente lo mismo que cualquier otro módulo del CPAN (o al menos eso parecen desde la perspectiva de un programador), así que cuando el lenguaje cambia simplemente se introducen pragmas. Un ejemplo son las nuevas características de Perl 5.10 que se activan usando un pragma.
La mayor incompatibilidad que ocurrió en la ultima década es la eliminación de los pseudohashes en Perl, y para minimizar los problemas se crearon mecanismos para facilitar la migración del código existente (use fields) y se mantuvieron los pseudohashes como una característica obsoleta por unos 5 años, lo que ilustra cuan comprometida está la comunidad a mantener la compatibilidad con el código existente.

La comunidad ha adoptado recientemente el término DarkPAN, que es todo el código que esta escrito en Perl, pero no está a la vista del público, sino que reside escondido en miles de sistemas que la gente ni siquiera sabe que han sido escritos en Perl (el que originó esta historia aparenta estar escrito en PHP), y aunque hay gente que prefiere que la plataforma de Perl se modernice obviando el DarkPAN, la comunidad en general piensa que esa no es la mejor política.

En el universo la materia obscura es la mayoría de la masa, y aunque no sabemos si el DarkPAN tiene más peso que el CPAN, podemos observar algunos efectos colaterales, por ejemplo: aunque parece haber más sistemas escritos en PHP y Python, se consiguen más ofertas de trabajo en Perl y probablemente la causa sea el DarkPAN.

Lo cierto es que todo ese código que mueve infinidad de negocios está allí porque funciona y porque se puede mantener en el tiempo con menos esfuerzo que el código desarrollado en otras plataformas.

Lo más impresionante es que hoy en día tengamos en la misma plataforma compatible con el código de hace una década, librerías tan fáciles de usar, modernas y poderosas como Moose, Devel::Declare y Catalyst, que son la envidia de los desarrolladores de otros lenguajes.

Así que si quieres programar en un ambiente fácil, moderno, poderoso y perdurable, la elección es clara usar Perl.

lunes, 5 de octubre de 2009

Calculadora Estadística: Toques finales

[English translation]
En el artículo anterior. dejamos pendiente lograr que el interpretador reconociera parámetros y fuera capaz de escribir cualquier valor de retorno de las funciones en Statistics::Descriptive.
Primero debemos definir como se separarán los comandos de los parámetros, y como se separarán los parámetros entre sí, y la manera más fácil es hacerlo como el shell, es decir usando espacios en blanco para ambos, así una vez que se obtiene una línea de comandos:
34     $command =~ s/^\s+//; $command =~ s/\s+$//;
35     my @args = split /\s+/, $command;
36     my $oper = looks_like_number( $args[0] ) ? "add_data" : shift @args;
se eliminan los espacios al principio y al final (34), se separan todos los elementos separados por uno o más blancos (35), y se obtiene la operación a realizar (36) que en general es el primer elemento, excepto en el caso de números en que la operación es "add_data". Luego de estas operaciones tenemos la operación a realizar en $oper y sus argumentos en @args, por lo que solamente resta aplicarlos:

38         when (%FUNCS)               { apply( $oper, @args ) }
La rutina a cargo de la aplicación de la operación debe obtener los argumentos (26), evaluar la operación en contexto lista (27), ¿por qué en contexto lista?, para permitir operaciones como "percentile" que produce múltiples valores.
Una vez obtenido el valor calculado, hay que verificar si vale la pena imprimirlo, así que retornamos si @result está vacío (28), o si tiene un solo elemento pero no esta definido o es una cadena vacia (29).
Para desplegar valores complejos utilizaré el formato YAML, porque es muy legible y tengo a disposición el módulo YAML del CPAN.
Así que dependiendo del número de elementos en @result convertiré el primer escalar o todo el arreglo en un texto en YAML antes de imprimirlo (30).
La calculadora de consola finalmente luce así:
 1 #!/usr/bin/perl
 2 
 3 use Modern::Perl;
 4 use Scalar::Util qw( looks_like_number );
 5 use Statistics::Descriptive;
 6 use Pod::Perldoc;
 7 use Term::ReadLine;
 8 use YAML;
 9 
10 my $term = new Term::ReadLine 'Statistic Calculator';
11 
12 my %FUNCS = map { $_ => 1 } qw( sum mean count variance standard_deviation
13     min mindex max maxdex sample_range median harmonic_mean geometric_mean
14     mode trimmed_mean clear add_data percentile quantile least_squares_fit
15     frequency_distribution_ref frequency_distribution);
16 
17 my @COMMANDS = qw( exit quit help man );
18 
19 sub help { say "Comandos: " . join( ", ", sort @COMMANDS, keys %FUNCS ) }
20 
21 sub man { Pod::Perldoc->new( args => \@_ )->process }
22 
23 my $s = Statistics::Descriptive::Full->new();
24 
25 sub apply {
26     my ( $oper, @args ) = @_;
27     my @result = $s->$oper(@args);
28     return unless @result;
29     return unless @result > 1 or (defined $result[0] and $result[0] ne "");
30     say YAML::Dump( @result == 1 ? $result[0] : \@result );
31 }
32 
33 while ( defined( my $command = $term->readline("Listo> ") ) ) {
34     $command =~ s/^\s+//; $command =~ s/\s+$//;
35     my @args = split( /\s+/, $command ) or next;
36     my $oper = looks_like_number( $args[0] ) ? "add_data" : shift @args;
37     given ($oper) {
38         when (%FUNCS)               { apply( $oper, @args ) }
39         when ("man")                { man "Statistics::Descriptive" }
40         when ( [ "exit", "quit" ] ) {last}
41         when ("help")               {help}
42         default                     { say "Error: tipee 'help' para ayuda" };
43     }
44 }
Después de está serie de artículos, espero que puedas apreciar, lo rápido que se puede trabajar en Perl, usando los mecanismos que ofrece el lenguaje y la inmensa cantidad de herramientas disponibles en el CPAN.
En futuras ocasiones utilizaré este ejemplo para ilustrar otras técnicas como la programación web, la orientación a objetos de Perl y por supuesto más módulos del CPAN.