domingo, 27 de septiembre de 2009

Calculadora Estadística: Consola Amigable

[English translation]
Una de las características que hacen agradable el uso de una aplicación de consola es la capacidad de editar la linea de comandos y de reutilizar los comandos anteriores, estas dos características hacen la aplicación mucho más amigable.  Así que vamos a agregarlas a la calculadora del artículo anterior.
Este es uno de esos trabajos donde el CPAN demuestra porque es la mejor ventaja de Perl, voy a usar Term::ReadLine, una interfaz unificada para lectura de consola, esta librería permite el uso de varios backends que son los que implementan la funcionalidad, y para que nuestro ejemplo funcione yo instalé Term::ReadLine::Perl, aunque supongo que Term::ReadLine::Gnu debería funcionar igual. Ambas son interfaces de la librería readline(3) que usan muchas aplicaciones, incluyendo bash.
Para tener una interfaz similar a la de bash solo tenemos que cambiar las líneas 27 y 28 de nuestro programa anterior por:
28     my $command = $term->readline("Listo> ") // last;
como siempre, se debe declarar el uso del módulo, e inicializar el terminal creando un objeto $term que usaremos para invocar el método readline:
 7 use Term::ReadLine;
 8 
 9 my $term = new Term::ReadLine 'Statistic Calculator';
usamos Term::ReadLine y no alguna de las variantes específicas, porque este se encarga de ejecutar las versiones específicas de manera automática, aunque permite también que el usuario mantenga control en caso de ser necesario. La librería tiene funciones que permiten adornar el prompt, autocompletar y algunas otras cosas, de nuevo el CPAN salva el día sin hacer más que leer la documentación de un módulo.
Voy a aprovechar para reparar algunas otras cosillas que andan molestando por allí (léase bugs), primero el más fastidioso y fácil de resolver: cuando damos un comando vacío sale un mensaje de error, porque una línea vacía no es capturada por ninguna clausula when, así que el remedio es fácil, agregamos:
when ("")  {  } # si el comando es vacío no hacer nada
y listo.
Otro problema más difícil de resolver son los comandos que no devuelven nada, como "clear", que nos dan un advertencia, gracias al use warnings implícito en Modern::Perl:
Listo> clear
Use of uninitialized value in concatenation (.) or string at calc1.pl line 32.
clear =
Para resolver este problema voy a crear una subrutina que aplique las funciones e imprima el resultado, para mantener el ciclo de despacho lo más claro posible, así que:
32   when (%FUNCS)  { say "$command = " . $s->$command }
pasará a ser esto:
32   when (%FUNCS)  { apply $command }
y luego implementaremos la subrutina apply, que recibe un comando, lo ejecuta y captura el resultado, pero se retorna a menos que el resultado esté definido y no sea una cadena vacia:
26     return unless defined $result and $result ne "";
La calculadora ahora esvmás amigable, sin embargo todavía tiene problemas, si ejecutan trimmed_mean se darán cuenta de que da un error, en el manual está la causa, la función trimmed_mean recibe 1 o 2 parámetros, pero por ahora la calculadora solo acepta comandos sin parámetros, en el próximo artículo veremos como agregarle parámetros y también desplegar resultados no escalares, como arreglos y hashes.
Nuestro programa completo ahora 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 
 9 my $term = new Term::ReadLine 'Statistic Calculator';
10 
11 my %FUNCS = map { $_ => 1 } qw( sum mean count variance standard_deviation
12     min mindex max maxdex sample_range median harmonic_mean geometric_mean
13     mode trimmed_mean clear );
14 
15 my @COMMANDS = qw( exit quit help man );
16 
17 sub help { say "Comandos: " . join( ", ", sort @COMMANDS, keys %FUNCS ) }
18 
19 sub man { Pod::Perldoc->new(args => \@_)->process }
20 
21 my $s = Statistics::Descriptive::Full->new();
22 
23 sub apply {
24     my $command = shift;
25     my $result = $s->$command;
26     return unless defined $result and $result ne "";
27     say "$command = $result";
28 }
29 
30 while (defined(my $command = $term->readline("Listo> "))) {
31     $command =~ s/^\s+//; $command =~ s/\s+$//;
32     given ($command) {
33         when ( looks_like_number($_) ) { $s->add_data($command) }
34         when (%FUNCS)                  { apply $command }
35         when ("man")                   { man "Statistics::Descriptive" }
36         when ( [ "exit", "quit" ] )    { last }
37         when ("help")                  { help }
38         when ("")                      { }
39         default                        { say "Error: tipee 'help' para ayuda" }
40     }
41 }

miércoles, 23 de septiembre de 2009

Calculadora Estadística: Usando el sistema

[English translation]
En el artículo anterior dejamos pendiente agregar un manual a nuestra calculadora, veamos algunas maneras de interactuar con el sistema de documentación en Perl para lograrlo.
El comando para ver la documentación en la calculadora sera "man" y como asumo que la mayoría debe haber interactuado con perldoc, voy a comenzar utilizando directamente este comando para mostrar un manual.
Perl desde hace mucho tiene la capacidad de ejecutar comandos del sistema de varias formas, una de ellas es el operador "`", si escribimos algo como:
1  my @out = `ls -l`
el arreglo @out terminará con cada una de las líneas de la salida del comando, también se pueden ejecutar comandos con open y utilizar su salida como entrada para un programa:
1 open $fd, "-|", "ls -ls" or die "Error: $!"
2 while ( readline $fd ) {
3     # $_ contiene una línea de la salida del comando
4 }
lo cual es sorprendente, aunque no muy recomendado en estos días, pero lo uso una y otra vez en el trabajo, donde arreglo montones de cosas con microprogramas en Perl.
Sin embargo hoy estoy interesado en la función "system" que voy a usar en la primera forma de agregarle un manual a la calculadora.
Como no quiero complicarme mostrando como se escribe un manual, por ahora usaré el manual del módulo Statistics::Descriptive, la solución es simplemente agregar una línea al programa del artículo anterior:
21  when ("man")  { system("perldoc Statistics::Descriptive") }
y eso eso es todo, así es Perl, no hay nada más fácil, algunos podrán decir que es sucio, pero definitivamente fue fácil. Cuando usamos "system" de esta manera perl envía el comando directamente al shell, así que es mejor usarlo así:
21  when ("man")  { system("/usr/bin/perldoc", "Statistics::Descriptive") }
al pasarle a system una lista de argumentos perl ejecuta directamente el comando, evitando algunos problemillas de seguridad que podrían ocurrir, pero el comando no se busca en el PATH, así que debes pasar la ruta completa al ejecutable.
No es una gran sorpresa que el comando perldoc está escrito en Perl, así que probablemente podemos reutilizar el código de este programa en nuestra calculadora, mirando dentro del programa te darás cuenta que perldoc es un programa muy simple, en efecto, las dos líneas importantes son:
1 use Pod::Perldoc;
2 Pod::Perldoc->run();
Asi que toda la funcionalidad de perldoc está metida dentro de un objeto de Perl!, esto es un patrón importante de la cultura Perl, pues permite reutilizar fácilmente cualquier aplicación en otra, que es exactamente lo que queremos hacer, desafortunadamente alguien olvidó documentar Pod::Perldoc así que me metí a ver como lo puedo integrar en mi programa, el resultado fue cambiar la línea 21:
21  when ("man") { Pod::Perldoc->new(args => ["Statistics::Descriptive"])->process }
y por supuesto declarar el uso de la clase al principio del programa:
 6 use Pod::Perldoc;
El único trabajo real fue aprender como funcionaba Pod::Perldoc y me tomó menos de 2 minutos (usando el excelente depurador de perl).
Finalmente me tomé el tiempo para refactorizar un poquito el programa, mejorar el comando "help" y al final que quedó 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 
 8 use constant SYNTAX_ERROR => "Error: tipee 'help' para ayuda";
 9 
10 my %FUNCS = map { $_ => 1 } qw( sum mean count variance standard_deviation
11     min mindex max maxdex sample_range median harmonic_mean geometric_mean
12     mode trimmed_mean clear );
13 
14 my @COMMANDS = qw( exit quit help man );
15 
16 sub help {
17     say "Comandos: " . join( ", ", @COMMANDS );
18     say "Funciones: " . join( ", ", keys %FUNCS );
19 }
20 
21 sub man {
22     Pod::Perldoc->new(args => \@_)->process
23 }
24 
25 my $s = Statistics::Descriptive::Full->new();
26 while (1) {
27     print "Listo> ";
28     my $command = readline(STDIN) // last;
29     $command =~ s/^\s+//; $command =~ s/\s+$//;
30     given ($command) {
31         when ( looks_like_number($_) ) { $s->add_data($command) }
32         when (%FUNCS)                  { say "$command = " . $s->$command }
33         when ("man")                   { man "Statistics::Descriptive" }
34         when ( [ "exit", "quit" ] )    { last }
35         when ("help")                  { help }
36         default                        { say SYNTAX_ERROR }
37     }
38 }
En el próximo articulo seguiremos agregando características a la calculadora para hacerla más amigable.

viernes, 18 de septiembre de 2009

Perl inteligente

[English translation]
En el artículo anterior vimos un ejemplo de Perl moderno, hoy vamos a profundizar un poquito más en la comparación inteligente de Perl 5.10 y como al combinarla con las características dinámicas del lenguaje obtenemos un programa ridículamente pequeño, pero más fácil de comprender y mantener.
Alguna vez leí (creo que de Paul Graham) que cuando alguna sección de código parece duplicada generalmente hace falta un nivel de abstracción, claro que él programa en Lisp, y tiene defmacro. Sin embargo Perl también tiene lo suyo, y en este caso nuestra primera solución podría basarse en un hash que incluya las funciones permitidas en nuestra calculadora:
 1 #!/usr/bin/perl
 2 
 3 use Modern::Perl;
 4 use Scalar::Util qw( looks_like_number );
 5 use Statistics::Descriptive;
 6 
 7 use constant SYNTAX_ERROR => "Error: tipee 'help' para ayuda";
 8 
 9 my %FUNCS = (
10     sum                => 0,
11     mean               => 0,
12     count              => 0,
13     variance           => 0,
14     standard_deviation => 0,
15     min                => 0,
16     mindex             => 0,
17     max                => 0,
18     maxdex             => 0,
19     sample_range       => 0,
20     median             => 0,
21     harmonic_mean      => 0,
22     geometric_mean     => 0,
23     mode               => 0,
24     trimmed_mean       => 0,
25 );
26 
27 my $s = Statistics::Descriptive::Full->new();
28 while (1) {
29     print "Listo> ";
30     my $command = readline(STDIN) // last;
31     $command =~ s/^\s+//; $command =~ s/\s+$//;
32     given ($command) {
33         when ( looks_like_number($_) ) { $s->add_data($command) }
34         when (/^(exit|quit)$/)         {last}
35         default {
36             if   ( exists $FUNCS{$command} ) { ... }
37             else                             { say SYNTAX_ERROR}
38         }
39     }
40 }
Esto es un avance importante, porque estamos simplificando el código en la parte complicada del programa, y reemplazándolo por una simple declaración de un hash, donde incluir una nueva función es tan simple como agregar una línea.
Claro que cualquier lector astuto se ha dado cuenta de que hago trampa porque el programa está incompleto; la línea 36 necesita una acción, nuestro problema ahora es como ejecutar el método correcto para la operación, y en Perl como de costumbre hay varias formas de hacerlo, una de ellas (la peor) podría utilizar referencias a las funciones de la clase en el hash, así:

10     sum  => \&Statistics::Descriptive::sum,
Para luego hacer algo como:

36     if ( exists $FUNCS{$command} ) { say "$command = " . $FUNCS{$command}($s) }
Digo que esa es la peor forma porque hay que saber mucho Perl para entender como funciona eso, y Perl tiene la capacidad de despachar métodos simbólicamente haciendo que nuestra intención quede perfectamente clara:
36     if ( exists $FUNCS{$command} ) { say "$command = " . $s->$command }
El costo de esta operación es mayor que el de la alternativa anterior, sin embargo es un precio que se paga con gusto, porque el programa es mucho más fácil de entender, y últimamente la gente es mucho más cara que las máquinas.
Finalmente si la flojera es uno de tus principios fundamentales, puedes reescribir la asignación del hash así:
 9 my %FUNCS = map { $_ => 0 } qw( sum mean count variance standard_deviation
10     min mindex max maxdex sample_range median harmonic_mean geometric_mean
11     mode trimmed_mean );
Lo que particularmente aprecio, porque ahorro puntuación (lo que parece abrumar a mucha gente) y tengo menos probabilidad de cometer un error de sintaxis.
Básicamente estoy construyendo una lista de palabras (los nombres de los métodos) con "qw", a partir de esta lista construyo otra (usando map) que contiene cada elemento de la lista original ($_) acompañado del número  0, perl convierte automáticamente esta lista en un hash donde cada nombre tiene asociado 0 como valor.
Si la explicación anterior te parece complicada o incomprensible, puedes ver la documentación de map en Perl, lo que te vendrá de maravilla porque además podrás aprender algo de programación funcional que de seguro te será muy provechoso.
Ahora me voy a deshacer del "if", prefiero los condicionales de múltiples vias, son más planos y se ve mejor el flujo, por eso opino que el given/when es lo mejor que le ha pasado a Perl en mucho tiempo, además me voy a deshacer de la expresión regular en la línea 34 por algo que tenga más sentido para un extraño a Perl:
 1 #!/usr/bin/perl
 2 
 3 use Modern::Perl;
 4 use Scalar::Util qw( looks_like_number );
 5 use Statistics::Descriptive;
 6 
 7 use constant SYNTAX_ERROR => "Error: tipee 'help' para ayuda";
 8 
 9 my %FUNCS = map { $_ => 1 } qw( sum mean count variance standard_deviation
10     min mindex max maxdex sample_range median harmonic_mean geometric_mean
11     mode trimmed_mean );
12 
13 my $s = Statistics::Descriptive::Full->new();
14 while (1) {
15     print "Listo> ";
16     my $command = readline(STDIN) // last;
17     $command =~ s/^\s+//; $command =~ s/\s+$//;
18     given ($command) {
19         when ( looks_like_number($_) ) { $s->add_data($command) }
20         when ( ["exit", "quit"] )      {last}
21         when (%FUNCS)                  { say "$command = " . $s->$command }
22         default                        { say SYNTAX_ERROR }
23     }
24 }
Ahora se ve mucho mejor (hasta parece Erlang).
Me estoy valiendo de algunas  funciones del smart matching que explicaré a continuación.
En la línea 20 se compara un valor contra un arreglo:
$command ~~ ["exit", "quit"]
Cuando se compara un escalar (a la izquierda) contra un arreglo (a la derecha) el efecto es equivalente a lo siguiente:
sub match_scalar_arrayref {
    my ($scalar, $arrayref) = @_;
    for my $item ( @$arrayref ) {
        return 1 if $scalar eq $item;
    }
    return undef;
}

Ya no recuerdo cuantas veces he escrito código como ese, o como este:
if ( grep { $scalar eq $_ } @$arrayref ) ...
Y que  ahora podré escribir con mas claridad y menos esfuerzo:
if ( $scalar ~~ $arrayref ) ...
Probablemente ya adivinaste que en la línea 21, el smart match entre un escalar y un hash es equivalente a:
if ( exists $hash{$scalar} ) ...

Un poco de tentación

Una sugerencia que recibí de un lector daba una solución todavía más corta y fácil de mantener, la idea era cambiar la línea:

21    when (%FUNCS) { say "$command = " . $s->$command }
por:
21    when ($s->can($command)) { say "$command = " . $s->$command }
El método can es provisto por la clase UNIVERSAL, de la cual derivan todos los objetos en Perl, y el propósito de este método es averiguar si un objeto o clase tiene un método determinado.
Al utilizar esta mejora ni siquiera necesito el hash %FUNCS, y además nuestro interpretador se actualizará automáticamente con nuevos comandos a medida que evolucione Statistics::Descriptive, lo cual suena muy bien desde el punto de vista de mantenibilidad, sin embargo, tiene un problema fatal para mí: no es seguro.
El problema es que pierdo el control sobre lo que Perl ejecuta automáticamente, y aunque probablemente este módulo no pueda hacer mucho daño, esta misma técnica utilizando algún otro módulo, podría ser peligrosa. Así que prefiero la seguridad y me quedo con el hash como mecanismo de despacho (y autorización de uso).
La moraleja es que se debe terner cuidado al utilizar mecanismos de control de ejecución dinámicos, sobre todo cuando se utilizan datos de fuentes externas no confiables en estos mecanismos de control de ejecución.

Completando el programa


La línea 22 da un error cuando no se conoce un comando, el mensaje dice que use "help" para obtener ayuda, pero el comando "help" todavía no está implementado, una manera rápida de implementarlo es:

23         when ("help") {
24             say "Los comandos válidos son: "
25                 . join( ", ", qw(exit quit help), keys %FUNCS )
26         }
Guau, eso fue fácil, lo mejor es que además de fácil es consistente, porque se utiliza la misma estructura de datos para informar los comandos, para seleccionarlos y para autorizarlos.
Una de las funciones que se me olvidó incluir en la calculadora en el artículo anterior fue "clear", agregar esta función ahora es tan sencillo como poner una nueva palabra en la definición de %FUNCS:
 9 my %FUNCS = map { $_ => 1 } qw( sum mean count variance standard_deviation
10     min mindex max maxdex sample_range median harmonic_mean geometric_mean
11     mode trimmed_mean clear )
Fue fácil, ¿o no?. Lo mejor es que el nuevo comando aparece automáticamente en la ayuda porque el programa es consistente.
Recapitulemos los logros del día, tenemos un programa:
  • Muy compacto
  • Fácil de comprender
  • Fácil de mantener
  • Consistente
  • Seguro
Perl tan válido como cualquier otro lenguaje, pero además, muy pocos lenguajes brindan mecanismos similares a los aquí expuestos para lograr un programa con estas características.
En el próximo artículo veremos como agregarle el manual de funciones estadísticas a nuestra calculadora con mucha facilidad.
A continuación los dejo con la versión final del programa:
 1 #!/usr/bin/perl
 2 
 3 use Modern::Perl;
 4 use Scalar::Util qw( looks_like_number );
 5 use Statistics::Descriptive;
 6 
 7 use constant SYNTAX_ERROR => "Error: tipee 'help' para ayuda";
 8 
 9 my %FUNCS = map { $_ => 1 } qw( sum mean count variance standard_deviation
10     min mindex max maxdex sample_range median harmonic_mean geometric_mean
11     mode trimmed_mean clear );
12 
13 my $s = Statistics::Descriptive::Full->new();
14 while (1) {
15     print "Listo> ";
16     my $command = readline(STDIN) // last;
17     $command =~ s/^\s+//;
18     $command =~ s/\s+$//;
19     given ($command) {
20         when ( looks_like_number($_) ) { $s->add_data($command) }
21         when (%FUNCS)                  { say "$command = " . $s->$command }
22         when ( [ "exit", "quit" ] )    {last}
23         when ("help") {
24             say "Los comandos válidos son: "
25                 . join( ", ", qw(exit quit help), keys %FUNCS )
26         }
27         default { say SYNTAX_ERROR };
28     }
29 }

miércoles, 16 de septiembre de 2009

Perl en cinco oraciones

Generalmente cuando escribo promoviendo Perl, lo hago a la defensiva, porque el estigma que le tienen encima es como exagerado, sin embargo me encontre este artículo y no pude resistir la tentación de copiar las oraciones, porque estan muy buenas.

Perl:
  1. Tiene una librería gigantesca de código reutilizable
  2. Tiene una cultura de mejores prácticas y pruebas de calidad
  3. Tiene una comunidad en crecimiento en la que todo el mundo es bienvenido
  4. Tanto Perl5 como Perl6 tienen un gran futuro.
  5. Durante los últimos 20 años ha sido un gran lenguaje para terminar los trabajos rápidamente y lo seguirá siendo por los próximos 20 años
Voy a sacarle punta a ese resumen ;-)

martes, 15 de septiembre de 2009

Usando Perl Moderno

[English translation]
Voy a intentar escribir una serie de artículos sobre Perl donde se pueda apreciar lo fácil y rápido que se pueden crear soluciones en esta plataforma.
Para ello elegí un diseño simple que me permite ilustrar una cantidad de técnicas y mejores prácticas, con un algoritmo accesible para cualquier programador aunque sea novato.
El programa de ejemplo será una calculadora estadística que en primera instancia utilizará un estilo tradicional, pero que se transformará poco a poco, haciéndose más flexible y fácil de mantener, mientras se aplican algunos mecanismos únicos del lenguaje y algunas librerías del CPAN.
El gran final es hacer que la calculadora sea una aplicación web utilizando un mecanismo sorprendente desarrollado sobre Perl. Una vez dicho esto comenzaré a usar Perl Moderno.
Haciendo honor al título del artículo, lo primero que hace nuestro programa es usar el módulo Modern::Perl, que es un atajo para decir:

use feature ':5.10';
use strict;
use warnings;
use mro 'c3';
Es decir, activa todas las características introducidas en Perl 5.10, también activa el modo estricto y las advertencias, y finalmente activa el orden de resolución de métodos utilizando el algoritmo C3. Como es de esperarse todos los ejemplos que veremos a lo largo de esta serie de artículos, solo funcionarán en Perl 5.10, porque intentare promover la mayor cantidad de características de esta nueva versión del lenguaje, así que: a instalar Perl 5.10.
Al usar Perl Moderno, se recomienda enfáticamente el uso de strict porque captura muchos errores comunes, entre ellos el uso accidental de referencias simbólicas, y los errores de tipográficos en las variables, al costo de que ahora deben ser declaradas con our (globales) ó my (léxicas), antes de usarse.
Por otra parte las advertencias permiten que perl nos informe acerca de posibles errores en la codificación. En perl 5.10 strict es más estricto y warnings tiene muchas advertencias nuevas, así que capturan más problemas que antes, lo que suele mejorar la calidad general del código y ahorrar tiempo de depuración.
En mi caso particular, cuando quise leer un comando y terminar el ciclo en caso de un fin de archivo escribí:

my $comando = readline(STDIN) or last;
Perl inmediatamente me advirtió que en algún caso podría confundirse undef (que denota el fin de archivo) con un "0" (cero), porque en perl "0" y undef se interpretan como falso. Una manera de escribir correctamente la instrucción sería:

defined (my $comando = readline(STDIN)) or last;
Pero aproveché para utilizar el nuevo operador // (defined or), con el que puedo escribir simplemente:

my $comando = readline(STDIN) // last;
El orden de resolución de métodos C3, resuelve algunos problemas existentes con el orden de resolución original de Perl, y lo recomendable es usarlo siempre en el nuevo código, esto no es del todo nuevo, hay módulos que usan este orden de resolución desde hace unos 4 años, solo que antes era un módulo del CPAN (Class::C3) y ahora tiene soporte nativo en el lenguaje.
Así que el primer consejo es usar Modern::Perl, porque activa una cantidad de características útiles y recomendadas de Perl.
Volviendo al programa, lo siguiente después de usar Modern::Perl es importar la subrutina looks_like_number() de Scalar::Util, que además de ahorrarme el trabajo de escribir las expresiones regulares para reconocer números, ahorra una buena cantidad de pánico de los lectores que podrían bloquearse de solo ver esas expresiones regulares.
El último módulo que se usa es el ingrediente principal de la calculadora, porque nunca pasó por mi mente escribir algoritmos de estadística, para eso existe el CPAN, que tiene de todo. En este caso usé Statistics::Descriptive, que sirve perfectamente a mi propósito.
En la línea 7 se declara una constante con un mensaje de error y en la 9 se define una variable con un objeto de la clase Statistics::Descriptive::Full, que será el estado de nuestra calculadora estádística, durante el ciclo de interpretación.
El ciclo principal es simple: lee un comando o se termina (last) si llegó el fin de archivo [línea 12], seguidamente se eliminan los espacios por la izquierda y la derecha del comando [línea 13], si el comando es un número se agrega al conjunto de datos del objeto Statistics::Descriptive::Full [línea 15] y si no, se selecciona y ejecuta un comando del interpretador.
La selección se hace con la nueva estructura de control de Perl 5.10 given/when [líneas 18-36] que efectúa smart matching entre el valor dado (given) y las clausulas de comparación (when). Como la comparación es "inteligente" depende de los operandos, y en general funciona como uno se lo imagina, sin embargo hay algunos casos rebuscados y nunca está de más leer los manuales.
Finalmente el nuevo operador say, que no es más que un print que emite un fin de línea, ayudando a evitar un montón de concatenaciones con "\n" y por ello contribuye con la claridad del código.

 1 #!/usr/bin/perl
 2 
 3 use Modern::Perl;
 4 use Scalar::Util qw( looks_like_number );
 5 use Statistics::Descriptive;
 6 
 7 use constant SYNTAX_ERROR => "Error: tipee 'help' para ayuda";
 8 
 9 my $s = Statistics::Descriptive::Full->new();
10 while (1) {
11     print "Listo> ";
12     my $command = readline(STDIN) // last;
13     $command =~ s/^\s+//; $command =~ s/\s+$//;
14     if ( looks_like_number($command) ) {
15         $s->add_data($command);
16     }
17     else {
18         given ($command) {
19             when ("sum")                { say "$command = " . $s->sum() }
20             when ("mean")               { say "$command = " . $s->mean() }
21             when ("count")              { say "$command = " . $s->count() }
22             when ("variance")           { say "$command = " . $s->variance() }
23             when ("standard_deviation") { say "$command = " . $s->standard_deviation() }
24             when ("min")                { say "$command = " . $s->min() }
25             when ("mindex")             { say "$command = " . $s->mindex() }
26             when ("max")                { say "$command = " . $s->max() }
27             when ("maxdex")             { say "$command = " . $s->maxdex() }
28             when ("sample_range")       { say "$command = " . $s->sample_range() }
29             when ("median")             { say "$command = " . $s->median() }
30             when ("harmonic_mean")      { say "$command = " . $s->harmonic_mean() }
31             when ("geometric_mean")     { say "$command = " . $s->geometric_mean() }
32             when ("mode")               { say "$command = " . $s->mode() }
33             when ("trimmed_mean")       { say "$command = " . $s->trimmed_mean() }
34             when (/^(exit|quit)$/)      {last}
35             default                     { say SYNTAX_ERROR }
36         }
37     }
38 }
Para usar la calculadora simplemente ejecutamos el archivo, aquí les muestro una corrida de prueba:

opr@toshi$ perl stat.pl
Listo> 19
Listo> 45
Listo> 24
Listo> 15
Listo> 39
Listo> 48
Listo> 36
Listo> count
count = 7
Listo> 10
Listo> 28
Listo> 30
Listo> count
count = 10
Listo> mean
mean = 29.4
Listo> standard_deviation
standard_deviation = 12.685950233756
Listo> salir
Error: tipee 'help' para ayuda
Listo> help
Error: tipee 'help' para ayuda
Listo> exit
opr@toshi$

Una mejora sencilla

Una mejora de estilo podría ser eliminar el if de la línea 15 y hacer la comparación en el given, esto además me permite mostrar que given topicaliza $_ al valor dado y que las clausulas when no solo comparan cadenas (usando eq) y expresiones regulares (usando =~), sino que permiten, entre otros, escribir expresiones booleanas utilizando $_ como un alias al valor dado en el given.

 1 #!/usr/bin/perl
 2 
 3 use Modern::Perl;
 4 use Scalar::Util qw( looks_like_number );
 5 use Statistics::Descriptive;
 6 
 7 use constant SYNTAX_ERROR => "Error: tipee 'help' para ayuda";
 8 
 9 my $s = Statistics::Descriptive::Full->new();
10 while (1) {
11     print "Listo> ";
12     my $command = readline(STDIN) // last;
13     $command =~ s/^\s+//; $command =~ s/\s+$//;
14     given ($command) {
15         when ( looks_like_number($_) ) { $s->add_data($command) }
16         when ("sum")                   { say "$command = " . $s->sum() }
17         when ("mean")                  { say "$command = " . $s->mean() }
18         when ("count")                 { say "$command = " . $s->count() }
19         when ("variance")              { say "$command = " . $s->variance() }
20         when ("standard_deviation")    { say "$command = " . $s->standard_deviation() }
21         when ("min")                   { say "$command = " . $s->min() }
22         when ("mindex")                { say "$command = " . $s->mindex() }
23         when ("max")                   { say "$command = " . $s->max() }
24         when ("maxdex")                { say "$command = " . $s->maxdex() }
25         when ("sample_range")          { say "$command = " . $s->sample_range() }
26         when ("median")                { say "$command = " . $s->median() }
27         when ("harmonic_mean")         { say "$command = " . $s->harmonic_mean() }
28         when ("geometric_mean")        { say "$command = " . $s->geometric_mean() }
29         when ("mode")                  { say "$command = " . $s->mode() }
30         when ("trimmed_mean")          { say "$command = " . $s->trimmed_mean() }
31         when (/^(exit|quit)$/)         {last}
32         default                        { say SYNTAX_ERROR }
33     }
34 }
Pienso que casi cualquier programador habituado a lenguajes dinámicos como Python o Ruby puede comprender sin dificultad código en Perl Moderno y sentirse cómodo trabajando con este.
Los programadores de lenguajes como C, C++,C# o Java, después de adaptarse a algunos conceptos deberían sentir una especie de experiencia liberadora, porque seguramente en cualquiera de ellos cuesta mucho más escribir un programa como este.
En el próximo artículo veremos algunas características dinámicas de Perl, que hacen el programa más corto, flexible y fácil de mantener.

martes, 8 de septiembre de 2009

Los Perleros no usan IE

Divertida conclusión de las estadísticas que me da Google
Analytics
, aunque más 33% de los visitantes del blog, utilizan
Windows como sistema operativo, solo el 7% llega navegando con IE,
a continuación las estadísticas.

Sistemas Operativos

Pos Sistema Rata
1. Linux 56,18%
2. Windows 33,71%
3. Macintosh 8,24%
4. FreeBSD 1,12%
5. SunOS 0,37%
6. SymbianOS 0,37%

Navegadores

Pos Navegador Rata
1. Firefox 44,94%
2. Mozilla 30,71%
3. Chrome 8,24%
4. Internet Explorer 7,12%
5. Safari 5,99%
6. Opera 3,00%

Ojalá que mas gente aprenda Perl ;-)

sábado, 5 de septiembre de 2009

El renacimiento de Perl

[Google English Translation]
En el año 2006 estaba coordinando 2 proyectos, uno de ellos era una aplicación web (obviamente en Catalyst), y parte de mi trabajo es buscar mejores herramientas para facilitar la labor de los programadores, así fue como me topé por primera vez con Moose, que aunque incipiente, captó mi atención de inmediato, así seguí los enlaces hacia Class::MOP, donde me hablaban del Meta Object Protocol, y por terminé aprendiendo algo de Common Lisp y particularmente CLOS.
Más o menos al mismo tiempo estaba coordinando el desarrollo de un sistema distribuido para Linux (en C++ por requerimiento del cliente), y también estaba en la búsqueda de alguna herramienta que aliviara el dolor de que nos causaba ese desarrollo, conocí Erlang y redescubrí la programación funcional, que me llevaron finalmente a OCaml, Haskell y Scheme.
Así que el 2006 y 2007 fueron años de diversión, aprendiendo nuevos lenguajes, mecanismos y paradigmas de programación, lo interesante es que a pesar de adquirir pericias en herramientas tan bien hechas y con una potencia impresionante como las mencionadas, todavía hoy, quien me ayuda a poner la comida en la mesa es principalemente Perl.
Algunos profetas del desastre predicen periódicamente la muerte de Perl, sin embargo, todavía se consigue más trabajo en Perl que en otros lenguajes similares, como se aprecia haciendo una búsqueda en indeed. Cuando les vengan con el cuento de que Perl está muerto no caigan por inocentes, porque en realidad está vivo y tomando impulso nuevamente. Algunos argumentarán que el crecimiento de los otros es más rápido, pero Perl tiene una base de muy grande de código en producción, y le es más difícil crecer, pero ya cruzo la línea de la inmortalidad.
Perl6, al que perdí de vista en el 2005, siguió su avance y logró renovar el interés en Perl5, cuya evolución reciente esta muy influenciada por el desarrollo de su sucesor. Por ejemplo: el sistema de OOP de Perl6 se implementó en Perl5 (Moose), instrucciones como el given/when, que aparece en Perl 5.10 y reemplazan al módulo Switch, vienen directamente de la sintaxis de Perl6.
Perl5 también evoluciona en la dirección del paradigma funcional, hace unos días leí la presentación Functional Pe(a)rls, dictada en el YAPC::EU::2009, así descubrí Foose: un proyecto que intenta introducir en Perl las características importantes de los lenguajes funcionales (principalmente Haskell), tales como listas flojas, mónadas y currificación, siempre cuidando la compatibilidad y la interacción con Moose.
Todos estos cambios interactúan y se retroalimentan del CPAN, la cantidad de módulos que utilizan Moose ha ido en aumento y ya Moose es una dependencia común para muchos módulos, incluyendo Catalyst.
Foose a su vez usa nuevos módulos como Devel::Declare, un módulo casi mágico que permite introducir nuevos elementos sintácticos en Perl, llevando la meta-programación en este lenguaje a un nuevo nivel de abstracción.
El excelente libro Perl Best Practices, ha sido un factor determinante en un cambio cultural de la comunidad, que ahora se enfatiza el buen estilo, la claridad del código y las interfaces, mientras se intenta dejar atrás en la historia a los JAPHs y la mala fama de lenguaje ilegible, por demás injustificada.
Haciendo alarde de su capacidad, la comunidad de Perl va mucho más allá que la de otros lenguajes, y elabora herramientas que permiten evaluar la calidad del código que se genera, así nacieron módulos como Perl::Critic y Test::Kwalitee, que evalúan las prácticas de programación y las critican permitiendo al programador elevar la calidad de su código, y como  consecuencia la mejora global de las librerías del CPAN.
En resumen, desde hace algún tiempo hay un proceso de renovación tecnológica y cultural que por definición son el Renacimiento de Perl.