Se desea que el contructor sea lo suficientemente polimorfo para admitir las siguientes formas de llamada:
lhp@nereida:~/Lperl/src$ perl -MNumber::Fraction -de 0 DB<1> x Number::Fraction->new(2,4) 0 Number::Fraction=HASH(0x84666a0) 'den' => 2 'num' => 1 DB<2> x Number::Fraction->new(2) # Un número. El denominador es 1 0 Number::Fraction=HASH(0x8527660) 'den' => 1 'num' => 2 DB<3> x Number::Fraction->new(2,0) # Error. El denominador es cero. Can't make a Number::Fraction with demominator 0 DB<4> $b = Number::Fraction->new # Sin número: es 0/1 DB<5> x $b->new # LLamado por un objeto. Se copia el objeto 0 Number::Fraction=HASH(0x85281f4) 'den' => 1 'num' => 0 DB<6> $b = Number::Fraction->new(2,5) # Dos números: numerador y denominador DB<7> x Number::Fraction->new($b) # Un argumento objeto: se copia 0 Number::Fraction=HASH(0x8528410) 'den' => 5 'num' => 2
Para simplificar la práctica aquí esta un constructor que cumple los requerimientos:
lhp@nereida:~/Lperl/src$ sed -ne '31,74p' Number/Fraction.pm | cat -n 1 { 2 my $isint = qr{^\s*-?\d+\s*$}; 3 my $isfrac = qr{^\s*(-?\d+)(/(-?\d+))?$}; 4 5 sub new { 6 my $class = shift; 7 8 my $self; 9 if (@_ >= 2) { 10 return unless $_[0] =~ $isint and $_[1] =~ $isint; 11 12 $self->{num} = $_[0]; 13 unless ($_[1]) { 14 carp "Can't make a $class with demominator 0"; 15 return; 16 } 17 $self->{den} = $_[1]; 18 } 19 elsif (@_ == 1) { 20 if (ref $_[0]) { # Number::Fraction->new($x) y $x objeto. Lo copiamos 21 return $class->new($_[0]->{num}, $_[0]->{den}) if (UNIVERSAL::isa($_[0], __PACKAGE__)); 22 croak "Can't make a $class from a ", ref $_[0]; 23 } else { # Es una cadena 'num/num' o 'num' 24 return unless $_[0] =~ $isfrac; 25 26 $self->{num} = $1; 27 $self->{den} = $3 || 1; 28 } 29 } 30 elsif (ref($class)) { # LLamado $x->new. Copiamos el objeto 31 %$self = %$class; 32 } 33 else { 34 $self->{num} = 0; 35 $self->{den} = 1; 36 } 37 38 bless $self, ref($class) || $class; 39 40 $self->normalise; 41 42 return $self; 43 } 44 }
A continuación sigue el código de normalise
. La función ''normaliza''
un quebrado, esto es lo reduce a una fracción irreducible.
sub normalise { my $self = shift; # Calculamos el máximo común divisor my $hcf = _hcf($self->{num}, $self->{den}); # ... y dividmos ambos por el hcf $self->{num} /= $hcf; $self->{den} /= $hcf; # Denominador positivo en forma normalizada if ($self->{den} < 0) { $self->{num} *= -1; $self->{den} *= -1; } }
La subrutina normalise
llama a _hcf
(del inglés
highest common factor o en español máximo común divisor)
para permitir la reducción de la fracción:
sub _hcf { my ($x, $y) = @_; ($x, $y) = ($y, $x) if $y > $x; return $x if $x == $y; while ($y) { ($x, $y) = ($y, $x % $y); } return $x; }
La subrutina _hcf
es ''puro cálculo'' por lo que la implementación
Perl es obviamente inferior en rendimiento a la correspondiente
versión C:
$ cat -n hcf.c 1 int hcf(int x, int y) { 2 int t; 3 4 if (y > x) { 5 t = x; x = y; y = t; 6 } 7 if (x == y) return x; 8 while (y) { 9 t = x; 10 x = y; 11 y = t % y; 12 } 13 return x; 14 }
Para poder llamar a la versión C de la subrutina desde Perl usando el módulo P5NCI lo único que necesitamos es crear una librería dinámica:
$ cc -shared hcf.o -o libhcf.so $ ls -ltr | tail -1 -rwxr-xr-x 1 lhp lhp 5446 2007-05-29 16:52 libhcf.so $ nm libhcf.so # nm nos lista los símbolos en la librería 00001650 A __bss_start 000003c0 t call_gmon_start 00001650 b completed.4463 00001558 d __CTOR_END__ 00001554 d __CTOR_LIST__ w __cxa_finalize@@GLIBC_2.1.3 000004f0 t __do_global_ctors_aux 000003f0 t __do_global_dtors_aux 00001648 d __dso_handle 00001560 d __DTOR_END__ 0000155c d __DTOR_LIST__ 00001568 a _DYNAMIC 00001650 A _edata 00001654 A _end 00000534 T _fini 00000450 t frame_dummy 00000550 r __FRAME_END__ 00001634 a _GLOBAL_OFFSET_TABLE_ w __gmon_start__ 0000048c T hcf # Nuestra función máximo común divisor 00000485 t __i686.get_pc_thunk.bx 0000036c T _init 00001564 d __JCR_END__ 00001564 d __JCR_LIST__ w _Jv_RegisterClasses 0000164c d p.4462
El módulo P5NCI permite cargar la librería desde Perl y llamar a las funciones:
$ cat -n usehcf.pl 1 #!/usr/local/bin/perl -w 2 use strict; 3 use P5NCI::Library; 4 5 my $lib = P5NCI::Library->new( library => './libhcf.so' ); 6 $lib->install_function( 'hcf', 'iii' ); 7 8 print hcf( 20, 10 ), "\n"; 9 print hcf( 12, 9 ), "\n"; 10 print hcf( 18, 12 ), "\n";La llamada a
P5NCI::Library->new
carga la librería.
La subsiguiente llamada al método install
busca la función en la librería
y crea una interfaz Perl para la especificación dada por la firma 'iii'
.
Esta firma indica que la función recibe dos enteros y devuelve un entero.
Observe que mediante P5NCI
es posible usar funciones cuyo
fuente no esta disponible. Tampoco importa en que lenguaje esté escrito.
Importa que dispongamos de la librería y que conozcamos el nombre y la
interfaz de la función.
El constructor admite la opción path
que permite especificar el
path de búsqueda para la librería. En este caso
podemos usar el ''nombre'' oficial de la librería (hcf
) y no
la especifciación completa. También dispone de una opción package
que
permite especificar el paquete en el que se aloja la función.
$ cat -n usehcf2.pl 1 #!/usr/local/bin/perl -w 2 use strict; 3 use P5NCI::Library; 4 5 my $lib = P5NCI::Library->new( library => 'hcf', path => '.' ); 6 print "Path de búsqueda:\n@DynaLoader::dl_library_path\n"; 7 $lib->install_function( 'hcf', 'iii' ); 8 9 print "hcf( 20, 10 ) = ",hcf( 20, 10 ), "\n"; 10 print "hcf( 12, 9 ) = ",hcf( 12, 9 ), "\n"; 11 print "hcf( 18, 12 ) = ",hcf( 18, 12 ), "\n";
La variable DynaLoader::dl_library_path contiene el camino de búsqueda de las librerías dinámicas.
Ahora al ejecutar el programa obtenemos el máximo común divisor para cada una de las tres parejas:
$ usehcf2.pl Path de búsqueda: . /usr/local/lib /lib /usr/lib hcf( 20, 10 ) = 10 hcf( 12, 9 ) = 3 hcf( 18, 12 ) = 6
La portabilidad de P5NCI es todavía insuficiente. El porcentaje de plataformas en las que funciona es bajo aún. Existen otros mecanismos para empotrar otros lenguajes en Perl: XS, Inline, etc. que son mas robustos.
El módulo Inline proporciona los medios para realizar las pasarelas entre el lenguaje Perl y otro lenguaje de programación. En particular Inline::C facilita la integración entre código C y código Perl. Veamos un ejemplo:
$ cat -n hcfinline.pl 1 #!/usr/local/bin/perl -w 2 use strict; 3 use Inline 'C'; 4 5 print hcf( 20, 10 ), "\n"; 6 print hcf( 12, 9 ), "\n"; 7 print hcf( 18, 12 ), "\n"; 8 9 __END__ 10 __C__ 11 12 int hcf(int x, int y) { 13 int t; 14 15 if (y > x) { 16 t = x; x = y; y = t; 17 } 18 if (x == y) return x; 19 while (y) { 20 t = x; 21 x = y; 22 y = t % y; 23 } 24 return x; 25 }El código C puede almacenarse dentro del fichero DATA (esto es, a partir de la aparición del marcador
__END__
en una sección determinada por el marcador __C__
.
También puede guardarse en una cadena ordinaria que es pasada a
Inline::C:
$ cat -n shcfinline.pl 1 #!/usr/local/bin/perl -w 2 use strict; 3 use Inline 'C' => q{ 4 int hcf(int x, int y) { 5 int t; 6 7 if (y > x) { 8 t = x; x = y; y = t; 9 } 10 if (x == y) return x; 11 while (y) { 12 t = x; 13 x = y; 14 y = t % y; 15 } 16 return x; 17 } 18 }; 19 20 print hcf( 20, 10 ), "\n"; 21 print hcf( 12, 9 ), "\n"; 22 print hcf( 18, 12 ), "\n";Esta segunda forma es preferible a la hora de realizar la práctica.
Ejecutémos dos veces el ejemplo y cronometremos los tiempos de ejecución:
$ time shcfinline.pl 10 3 6 real 0m1.797s user 0m1.520s sys 0m0.250s $ time shcfinline.pl 10 3 6 real 0m0.065s user 0m0.060s sys 0m0.010sVemos que la segunda vez tarda menos que la primera. Esto es así porque la primera vez Inline genera una librería dinámica que - por defecto - se guarda en el subdirectorio
__Inline
:
$ ls -ltr | tail -1 drwxr-xr-x 4 lhp lhp 4096 2007-05-31 07:42 _Inline $ tree _Inline/ _Inline/ |-- build |-- config `-- lib `-- auto `-- shcfinline_pl_677d |-- shcfinline_pl_677d.bs |-- shcfinline_pl_677d.inl `-- shcfinline_pl_677d.so 4 directories, 4 filesAdemás genera el código de traducción de las llamadas desde Perl a las funciones C.
Durante las ejecuciones posteriores Inline
comprueba
que el código fuente no ha cambiado. Si es así carga la librería dinámica
generada.
La gramática de Inline para C
reconoce ciertas definiciones
de funciones del código C
. Esto es, Inline
generará
el código necesario para enlazar la llamada a la función como si fuera una
subrutina Perl. Si no puede reconocer la definición, esta será
ignorada sin que hayan mensajes de error o advertencia. No quedará
disponible en el ámbito de Perl, pero podrá aún seguir siendo
usada en el ámbito de C.
Inline busca por definiciones de funciones de estilo:
tipo_de_retorno nombre_de_funcion ( pares_tipo_identificador ) { cuerpo }
en las parejas tipo identificador, ...
puede usarse cualquier tipo
que esté definido en el fichero typemap.
Las siguientes definiciones no serán reconocidas:
Foo(int i) # no hay tipo de retorno int foo(float f) { # no existe definición en typemap para float int Foo(num) double num; { # la vieja sintáxis C no se soporta void Foo(void) { # void se permite solo para el retorno
Inline no está limitado a C y puede ser usado con otros lenguajes. El siguiente ejemplo muestra como usar Inline::Python para usar código Python desde C:
lhp@nereida:~/Lperl/src/testing$ cat -n python.pl 1 #!/usr/bin/perl -w 2 use strict; 3 4 use Inline Python => <<'END_OF_PYTHON_CODE'; 5 def add(x,y): 6 return x + y 7 8 def subtract(x,y): 9 return x - y 10 11 END_OF_PYTHON_CODE 12 13 print "9 + 16 = ", add(9, 16), "\n"; 14 print "9 - 16 = ", subtract(9, 16), "\n";Al ejecutar este programa obtenemos la siguiente salida:
lhp@nereida:~/Lperl/src/testing$ python.pl 9 + 16 = 25 9 - 16 = -7
Cuando escriba esta práctica haga uso de P5NCI
o de Inline
para la construcción de las funciones numéricas
con prototipos simples que aparezcan durante la construcción del módulo.
Basta con que considere la función del cálculo del máximo común divisor.
Si se siente realmente fuerte y capacitado puede leer
la sección como retornar múltiples valores del Inline::C Cookbok
para ver como implantar la suma y el producto de fracciones.
Este es un ejemplo de como hacerlo: Se deben empujar - usando Inline_Stack_Push - en la pila del intérprete Perl los valores de retorno:
void add_c(int n1, int d1, int n2, int d2) { n1 = n1*d2 + n2*d1; d1 = d1*d2; Inline_Stack_Vars; Inline_Stack_Reset; Inline_Stack_Push(sv_2mortal(newSViv(n1))); Inline_Stack_Push(sv_2mortal(newSViv(d1))); }La función Inline_Stack_Vars define un conjunto de variables internas que son necesarias para poder acceder a la pila del intérprete Perl.
En general, cuando la función sea de tipo void
como es el caso del ejemplo,
se deben usar las macros con nombre Inline_Stack_. También si la función
tiene un número variable de argumentos (uso de la elípsis ...
).
Antes de comenzar a empujar items en la pila se debe llamar a la función Inline_Stack_Reset. Inicializa el puntero de pila interno al comienzo de la pila.
A partir del númerador n1 = n1*d2 + n2*d1
debemos construir
el escalar Perl que guarda el número. La función
newSViv es la que lo permite.
La notación newSViv
significa new Scalar
Value from an Integer Value.
El valor retornado por esta función es la estructura de datos que representa
un valor escalar Perl, conocido en la jerga como SV (véase perlguts).
Existe una familia de constructores de valores escalares
SV
a partir de diferentes tipos de fuentes:
newSVuv para enteros sin signo, newSVnv para doubles
,
etc. (véase perlapi).
Como sabemos el recolector de basura de Perl usa un contador de
referencias para saber que valores están en desuso y liberar la
memoria correspondiente.
La función sv_2mortal marca el SV
recién creado como mortal
para que su memoria pueda ser liberada por el sistema de gestión de memoria
cuando sea seguro hacerlo.
Use Test::LectroTest en la generación de las pruebas. Construya un generador de fracciones usando Test::LectroTest::Generator.