Sobrecarga de las Constantes

El Problema

El paquete que desarrollaremos en la práctica 6.8.5 permite trabajar cómodamente con números fraccionarios. Sin embargo, la forma de crearlos implica el uso explícito del constructor:

my $d = fraction->new(4,5);
Mientras que para un número podemos escribir directamente $d = 4, ya que Perl es capaz de deducir el tipo. Sería bueno escribir nuestra práctica de manera que constantes de tipo cadena o numéricas conteniendo fracciones fueran convertidas en objetos de tipo fracción.

La Subrutina overload::constant

Para cambiar el modo en que Perl interpreta las constantes enteras, flotantes, cadenas y expresiones regulares podemos crear un conjunto de ''manejadores'' mediante la subrutina overload::constant.

Se espera que dicho manejador devuelve un valor escalar que es usado en lugar de la interpretación normal. Por ejemplo, para usar overload::constant en el paquete Math::BigFloat para modificar el modo en que las constantes enteras y flotantes se interpretan en Perl haríamos:

package Math::BigFloat;
use Math::BigInt;
use overload;

my %_constant_handlers = (
  integer => sub { return Math::BigInt->new($_[0]) },
  float   => sub { return Math::BigFloat->new($_[0]) }
);

sub import { overload::constant %_constant_handlers }
sub unimport { overload::remove_constant %_constant_handlers }
Obsérve el uso de import (véase sección 5.7) que como sabemos es ejecutada cada vez que se usa (use Math::BigFloat) el módulo. Aquí la función de import no es exportar símbolos sino hacer que se ejecute la llamada overload::constant %_constant_handlers produciendo la consiguiente modificación de la interpretación de las constantes enteras y flotantes.

Argumentos de overload::constant

La subrutina overload::constant toma como argumento un hash y espera que las entradas tengan una de las siguientes claves:

Los Valores: Argumentos de un Manejador de Constantes

El correspondiente valor para cada clave debe ser una referencia a una subrutina. La subrutina es responsable de proporcionar un valor final al tipo particular de constante que esta siendo interpretado. Se le pasan tres parámetros:

Invocación de los manejadores de constantes


Tabla: Invocación de los manejadores de constantes
Constante Manejador Argumentos  
"doble comi" q ('doble comi', 'doble comi', 'q')  
qq{qq comi} q ('qq comi', 'qq comi', 'qq')  
'comi simples' q ('comi simples', 'comi simples', 'q')  
q{q comi} q ('q comi', 'q comi', 'q')  
qw{qw comi} q ('qw comi', 'qw comi', 'q')  
<<HERE 
docu  
HERE
q ("docu \n", "docu \n", 'qq')  
<<'HERE'
com docu 
HERE
q ("com docu \n", "com docu \n", 'qq')  
tr/desde/hacia/
q ('desde', 'desde', 'tr')  
  q ('hacia', 'hacia', 'tr')  
qr{qr comi} qr ('qr comi', 'qr comi', 'qq')  
s/s_pat/s_text/
qr ('s_pat', 's_pat', 'qq')  
  q ('s_text', 's_text', 's')  
m/m patron/ qr ('m patron', 'm patron', 'qq')  
12345 integer ('12345', 12345, undef)  
12_345 integer ('12_345', 12345, undef)  
12345.0 float ('12345.0', 12345.0, undef)  
12345e1 float ('12345e1', 123450.0, undef)  
012345 binary ('012345', 5349, undef)  
0x12345 binary ('0x12345', 74565, undef)  
0xBadDeed binary ('0xBadDeed', 195941733, undef)  


La cadena fuente pasada como tercer argumento del manejador esta definida únicamente para los manejadores q y qr. Para esos manipuladores toma uno de los siguientes valores:

Ejemplo: el Módulo Number::Fraction

El módulo Number::Fraction permite la sobrecarga de constantes. Véase un ejemplo:

lhp@nereida:~/Lperl/src$ cat -n fractions.pl
 1  #!/usr/bin/perl
 2  use warnings;
 3  use strict;
 4  use Number::Fraction ':constants';
 5
 6  no warnings 'numeric';
 7  my $trescuartos = '1/2'+'1/4';
 8  print "$trescuartos\n";
 9
10  my $x = '1hola'+2;
11  print "$x\n";
12
13  no Number::Fraction;
14  $trescuartos = '1/2'+'1/4';
15  print "$trescuartos\n";
La salida de este programa es:
$ ./fractions.pl
3/4
3
2
La transformación de las constantes ocurre en tiempo de compilación como muestran las siguientes ejecuciones con B::Terse6.1

$ perl -MO=Terse,-exec \
       -MNumber::Fraction=':constants' \
       -e '$x = "1/2"'
OP (0x81ce758) enter
COP (0x824c708) nextstate
SVOP (0x8290460) const [2] RV (0x8298d88) \HASH
PADOP (0x824c308) gvsv  GV (0x814f648) *x
BINOP (0x8235818) sassign
LISTOP (0x8235048) leave [1]
-e syntax OK
$ perl -MO=Terse,-exec -e '$x = "1/2"'
OP (0x81570b8) enter
COP (0x816c5a0) nextstate
SVOP (0x81571a8) const [2] PV (0x814f5dc) "1/2"
PADOP (0x816c740) gvsv  GV (0x814f660) *x
BINOP (0x816c880) sassign
LISTOP (0x816c708) leave [1]
-e syntax OK

Esta sobrecarga se consigue definiendo import en la línea 22 del listado que sigue a continuación, para que llame a overload::constant sobre el hash %_const_handlers.

En este caso el hash tiene una única clave, ya que sólo estamos interesados en tratar las constantes de tipo cadena. El constructor new será especificado en la sección 6.8.5: Cuando recibe la cadena produce el objeto Number::Fraction.

 1 package Number::Fraction;
 2 
 3 use 5.006;
 4 use strict;
 5 use warnings;
 6 use Carp;
 7 
 8 our $VERSION = sprintf "%d.%02d", '$Revision: 1.34 $ ' =~ /(\d+)\.(\d+)/;
 9 
10 use overload
11   q("") => 'to_string',
12   '0+' => 'to_num',
13   '+' => 'add',
14   '*' => 'mult',
15   '-' => 'subtract',
16   '/' => 'div',
17   fallback => 1;
18 
19 my %_const_handlers =
20   (q => sub { return __PACKAGE__->new($_[0]) || $_[1] });
21 
22 sub import {
23   overload::constant %_const_handlers if $_[1] and $_[1] eq ':constants';
24 }

El import proveído hace que, en el caso de que el constructor no devuelva un objeto válido, se devuelva el segundo argumento, que es el valor que Perl le da por defecto a la constante. El resultado es la conversión correcta de aquellas cadenas que pueden ser interpretadas como fracciones y que otras cadenas como '1hola' sean interpretadas de acuerdo a las tradiciones de Perl.

Nótese la condición para la importación en la línea 23: if $_[1] and $_[1] eq ':constants'. Esto significa que para activar el manejador de constantes de tipo cadena el programa cliente ha de declarar:

use Number::Fraction ':constants';

esto es una buena idea: cambiar el modo en que funcionan las constantes es un cambio lo suficientemente trascendental como para sólo hacerlo bajo petición explícita del usuario.

Seguimos:

26 sub unimport {
27   overload::remove_constant(q => undef);
28 }
29 
30 sub new {
31  ...
32 }

Recuerde que la función unimport se ejecutará cuando el usuario haga una llamada en su programa a no Number::Fraction. En este caso la negación produce el abandono del manejo de las constantes.



Subsecciones
Casiano Rodríguez León
2009-10-04