Este capitulo cubre el diseño de módulos personalizados.
Módulos personalizados sirven para crear y usar objetos en vez de subrutinas,
para evitar "cut n' paste" de código de Perl entre programas.
Este capítulo trata de hacer un resumen breve de módulos y objetos,
pero por supuesto no se puede cubrir todo sobre este asunto enorme en un sólo capítulo.
Para más información, hay bastante documentación sobre modulos y objetos en perldoc:
perlmod Perl modules: how they work
perlmodlib Perl modules: how to write and use
perlmodinstall Perl modules: how to install from CPAN
perltoot Perl OO tutorial
perlobj Perl objects
perltie Perl objects hidden behind simple variables
perlbot Perl OO tricks and examples
La función package se usa para definir un llamado "namespace".
En un "namespace" cada variable es único, así se puede usar dos variable distintos
con el mismo nombre, cada uno en su namespace.
El namespace 'main' es el default namespace, así todos los programas de Perl usan la declaración package main; por default, aunque no esté especificado en el programa.
El ejemplo abajo muestra el uso de package con namespaces diferentes:
El varible __PACKAGE__ forma parte de Perl, y contiene el nombre del namespace actual.
Nota que no se usa use strict en el ejemplo arriba.
Esto es para poder definir variables únicos en un namespace, variables "públicos".
(Veremos como se combina use strict con variables de namespaces más adelante.)
Los variables declarados con my son accesibles dentro de un llamado "scope", pero no fuera del mismo.
El scope es el bloque actual donde se ejecuta el código.
El scope puede ser un loop, una subrutina o un programa entero, o sea, un archivo.
La función use se usa para accesar un módulo desde un programa.
Un módulo es un archivo de texto con código Perl que termina en .pm (Perl Module) en vez de .pl.
El ejemplo arriba (un poco modificado) se puede realizar creando tres archivos, Mama.pm, Papa.pm y programa.pl:
Mama.pm:
# Namespace 'Mama'
package Mama;
$edad = 50;
my $secret = 'secreto de mama';
1;
Papa.pm:
# Namespace 'Papa'
package Papa;
$edad = 55;
my $secret = 'secreto de papa';
1;
programa.pl:
#!/usr/bin/perl
use strict;
use Mama;
use Papa;
my $edad = 25;
print 'Mi edad: ', $edad, "\n";
print 'La edad de mi Mama: ', $Mama::edad, "\n";
print 'La edad de mi Papa: ', $Papa::edad, "\n";
print "Mis papas tienen secretos declarados con 'my':\n";
print '$Mama::secreto no es accesible desde aca', "\n";
print '$Papa::secreto tampoco', "\n";
La linea use Mama; busca el archivo de nombre Mama con extensión .pm, y luego importa el namespace Mama con todos los subrutinas y variables públicos que tenga este módulo.
La función use indica éxito con True, por eso la última linea de un módulo tiene que evaluar a True, por esto todos los módulos terminan con 1;.
Nota que en el ejemplo arriba el variable $secret está declarado con my, o sea privado, quiere decir que solo está accesible dentro del scope del archivo Mama.pm, y no desde programa.pl, mientras $edad está declarado como público, y por lo tanto accesible desde programa.pl.
Un ejemplo un poco más útil, la implementación de trim: Trim.pm:
#!/usr/bin/perl
use Trim;
my $s = ' hola ';
my $s2 = Trim::trim($s);
my $s3 = Trim::ltrim($s);
my $s4 = Trim::rtrim($s);
print "$s\n";
print "$s2\n";
print "$s3\n";
print "$s4\n";
Los ejemplos arriba solo muestran como separar los archivos, todavía no usamos use strict en los módulos, y para usar la función trim tenemos que especificar el nombre completo Trim::trim.
Veremos como diseñar la interface de un módulo para evitar esto.
Como ejemplo de una interface de un módulo a crear el archivo SP/Tools.pm.
La interface debe tener las siguientes lineas:
package SP::Tools;
use strict;
use vars qw(@ISA @EXPORT @EXPORT_OK $VERSION);
use Exporter;
@ISA = qw(Exporter);
@EXPORT = qw(trim);
@EXPORT_OK = qw(ltrim rtrim);
$VERSION = 1.00;
luego el código:
sub ltrim {...}
sub rtrim {...}
sub trim {...}
y el archivo siempre debe terminar con: 1;
para devolver True.
Explicación de la interface linea por linea:
package SP::Tools;
El nombre SP::Tools se refiere al archivo SP/Tools.pm, que es el path donde Perl busca por el módulo al ejecutar use.
Perl no busca todo el disco por el módulo, sólo en los paths que están definidos en el arreglo global de Perl @INC, entre ellos /usr/local/lib/perl5/site_perl/5.005 y /usr/libdata/perl/5.00503.
El path actual (.) siempre está definido en @INC, así el programa en el ejemplo arriba programa2.pl encuentra al módulo Trim.pm, por que los dos están en el directorio actual.
En el caso de SP::Tools hay que crear un subdirectorio SP donde se coloca Tools.pm.
Sin embargo, si se ejecuta el programa desde /otro/directorio, busca por SP/Tools.pmen el path actual (.) que corresponde a /otro/directorio/SP/Tools.pm (que no existe), así el programa falla.
Módulos de CPAN siempre se instalan en uno de los directorios /usr/local/lib/perl5/site_perl/5.005 y /usr/libdata/perl/5.00503, paths absolutos incluidos en @INC, así programas que usan módulos de CPAN los encuentran independente del path actual.
use strict; strict no es un módulo, sino un llamado pragma (compiler directives), siempre se debe usar por seguridad y facilidad de hacer debug, leer más en en perldoc strict
use vars qw(@ISA @EXPORT @EXPORT_OK $VERSION);
Cuando se usa use strict normalmente no se permite el uso de variables globales (que normalmente nunca se debe usar como programador profesional). Sin embargo, en este caso queremos combinar un módulo seguro y facil de hacer debug (use strict;) pero necesitamos exportar ciertos variables (hacerlos globales) para que sean accesibles desde fuera del módulo, o sea desde nuestros programas.
El use strict; nos permite hacer esto siempre y cuando declaramos explicitamente que variables vamos a exportar (hacer globales).
use Exporter;
Cuando un programa ejecuta use SP::Tools trata de importar las variables públicas usando el método SP::Tools->import().
Nuestro SP::Tools no incluye tal método import, pero lo importamos del módulo Exporter.
Leer más en perldoc Exporter.
@ISA = qw(Exporter);
Se lee esta linea como: Is a Exporter.
Cuando un programa usa SP::Tools no encuentra el método import en SP::Tools, revisa el arreglo estandar de Perl @ISA, donde encuentra que debe buscar en Exporter para encontrar tal método.
Para que el programa pueda revisar @ISA, hay que exportarlo como hicimos con use vars.
En otras palabras, esta es una forma de heredar métodos de otros módulos.
@EXPORT = qw(trim);
Cuando un programa ejecuta use SP::Tools; todos los métodos listados en @EXPORT se importa por default, así se puede accesar la funcíon trim en vez de SP::Tools::trim.
La lista de métodos exportados por default se debe usar con moderación, como el nombre de un método en este módulo puede causar conflicto con un método de otro módulo, cuando un programa usa dos o más módulos.
@EXPORT_OK = qw(ltrim rtrim);
Los métodos listados en @EXPORT_OK no se exportan al programa por default, pero como dice el nombre, es OK a exportarlos si así se especifica en el programa así.
Estos metodos se importan al programa con use SP::Tools qw(ltrim);.
$VERSION = 1.00;
Con $VERSION se especifica la versión de este módulo.
Como con cualquier software, una versión nueva contiene más funciones que la anterior, o correcciones de bugs en funciones existentes.
Si por ejemplo una versión anterior de un módulo ($VERSION = 0.666) contenía un bug grave que formatea el disco duro bajo ciertas circunstancias, pero en las versiones más recientes ($VERSION = 1.00, $VERSION = 1.01) esto está arreglado, un programa puede asegurarse que está usando la versión 1.00 o mayor con use SP::Tools 1.00;.
Si $VERSION es igual o mayor que 1.00 el programa se ejcuta como normal, si es menor que 1.00 el programa termina con un mensaje de error:
SP::Tools version 1 required--
this is only version 0.666 at programa3.pl line 3.
BEGIN failed--compilation aborted at programa3.pl line 3.
Nota: Se puede combinar $VERSION con los métodos en @EXPORT_OK: use SP::Tools 1.00 qw(ltrim);
OK, suficiente de teoría, aquí el código de SP/Tools.pm, trim.pl y ltrim.pl:
SP/Tools.pm:
# SP/Tools.pm
package SP::Tools;
use strict;
use vars qw(@ISA @EXPORT @EXPORT_OK $VERSION);
use Exporter;
@ISA = qw(Exporter);
@EXPORT = qw(trim);
@EXPORT_OK = qw(ltrim rtrim);
$VERSION = 1.00;
sub ltrim {
my $string = shift;
$string =~ s/^\s*//g;
return $string;
}
sub rtrim {
my $string = shift;
$string =~ s/\s*$//g;
return $string;
}
sub trim {
my $string = shift;
$string =~ s/^\s*(\S+)\s*$/$1/g;
return $string;
}
1;
trim.pl:
#!/usr/bin/perl
use strict;
use SP::Tools;
my $s = ' hola ';
my $s2 = trim($s);
print "$s\n";
print "$s2\n";
ltrim.pl:
#!/usr/bin/perl
use strict;
use SP::Tools 1.00 qw(ltrim);
my $s = ' hola ';
my $s2 = ltrim($s);
print "$s\n";
print "$s2\n";
Tarea: Leer perldoc Exporter y perdoc -f qw, hacer un módulo que exporta tres funciones.
Cuando se crea un programa programa.pl y el módulo Modulo.pm no es muy práctico ser obligado de correr el programa desde el mismo directorio (ve arriba la explicación de @INC, llamado el Default Perl Search Path).
Se puede agregar paths no estandar a @INC con use lib qw(/my/path/to/modules);.
Así el directorio /my/path/to/modules está incluido al buscar módulos.
Se coloca Modulo.pm en /my/path/to/modules, el programa programa.pl puede estar en otro directorio, por ejemplo /my/path/to/programs, y se puede ejecutar /my/path/to/programs/programa.pl desde cualquier directorio.
Este método también se puede practicar con módulos de CPAN.
Si un programador de Perl no tiene acceso como root a una máquina UNIX, no puede instalar módulos de CPAN en el directorio estandar, pero puede instalarlso en otro directorio.
Al instalar un módulo desde CPAN manualmente, normalmente se escribe perl Makefile.PL
Se cambia a perl Makefile.PL PREFIX=/my/path/to/modules, y el módulo se instala en /my/path/to/modules.
Supongamos que se instaló WWW::Search::Google en /my/path/to/modules.
Un programa puede accesar este módulo de esta manera:
#!/usr/bin/perl -w
use strict;
use lib qw(/my/path/to/modules);
use WWW::Search::Google;
# Aqui va el codigo...
La herramienta h2xs es el sueño ideal para el arragán.
Luego de crear SP/Tools.pm sería bonito distribuirlo de la misma manera como se encuentran los módulos en CPAN, empacados y comprimidos, con documentación, programas de pruebas para hacer make test, etc.
El programa h2xs crea plantillas donde se inserta el código de un módulo ya hecho.
Las plantillas indican donde pegar el código, donde escribir la documentación del módulo y donde crear programas de prueba (normalmente en test.pl).
Una de las plantillas es Makefile.PL, sirve para crear la distribuición, ni hay que modificarla.
Abajo la secuencia de comandos de shell para empacar el módulo SP::Tools:
# Crear el subdirectorio SP/Tools del directorio actual,
# y todas las plantillas
h2xs -XA -n SP::Tools
# Cambiar al directorio creado
cd SP/Tools/
# Insertar el código ya hecho y escribir la documentación,
# todo en Tools.pm
# Agregar código de prueba a test.pl
perl Makefile.PL
# Crear el archivo SP-Tools-0.01.tar.gz
# (si no cambiaste $VERSION)
make dist
# Listo para distribuición!
Tarea: Leer perldoc h2xs, visitar CPAN para ver como se registra en CPAN para publicar un módulo.
Nota: Todo lo que hemos tratado aquí son packages y modules sirviendo como librerías, no como classes y objects.
Classes y objects forman parte de Capítulo 5.