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.

No hay comentarios:

Publicar un comentario