Supongamos que queremos escribir un módulo que permite trabajar con números en punto flotante de tamaño arbitrario. Su uso sería algo así:
#!/usr/bin/perl -w use strict; use Math::BigFloat; my $a = Math::BigFloat->new('123_456_789_123_456_789'); my $y = $a->copy()/1_000_000_000; print "a = $a\n"; print "-a = ",-$a,"\n"; print "y = $y\n"; print "a+y = ",$a+$y,"\n";cuya ejecución nos da:
$ ./bigfloat.pl a = 123456789123456789 -a = -123456789123456789 y = 123456789.123456789 a+y = 123456789246913578.123456789y queremos que el módulo, como ilustra el ejemplo, sobrecargue las operaciones binarias y unarias usuales así como el uso de las constantes.
Los mecanismos para escribir un módulo como este
los proporciona el módulo overload.pm debido a Ilya Zakharevich,
el cual se incluye en la distribución estandard de Perl.
Este módulo permite la sobrecarga de operadores. Para sobrecargar los operadores
para una clase dada, hay que pasarle a la sentencia use
una lista de
pares operador => referencia a código
:
package Math::BigFloat; use overload "*" => \&fmul, "+" => "fadd", "neg" => sub { Math::BigInt->new($_[0]->fneg()) };
Cada pareja consiste de una clave, que especifica el operador a sobrecargar,
y una referencia a una subrutina, que será invocada cuando se encuentre
el operador. La clave neg
corresponde al operador de negación unaria.
La clave puede ser cualquiera de las reseñadas en la tabla 6.8.
La referencia a la subrutina puede ser
La subrutina de implementación es llamada cada vez que un objeto de la clase
en cuestión (en el ejemplo la clase Math::BigFloat
) es un operando
del operador correspondiente.
Por ejemplo, si ejecutamos con el depurador el programa:
$ cat -n ./bigfloat2.pl 1 #!/usr/bin/perl -d 2 use strict; 3 use Math::BigFloat; 4 5 my $a = Math::BigFloat->new('123_456_789_123_456_789'); 6 my $y = $a->copy()/1_000_000_000; 7 8 print "a+y = ",$a+$y,"\n";observaremos como la subrutina asociada con el
+
es llamada:
$ ./bigfloat2.pl main::(./bigfloat2.pl:5): my $a = Math::BigFloat->new('123_456_789_123_456_789'); DB<1> n main::(./bigfloat2.pl:6): my $y = $a->copy()/1_000_000_000; DB<1> main::(./bigfloat2.pl:8): print "a+y = ",$a+$y,"\n"; DB<1> sHemos pulsado
s
para entrar en la subrutina ...
Math::BigInt::CODE(0x82f9acc)(/usr/share/perl5/Math/BigInt.pm:50): 50: '+' => sub { $_[0]->copy()->badd($_[1]); }, DB<1> p "@_" 123456789123456789 123456789.123456789Observe los dos argumentos: los dos números grandes. Seguimos ...
DB<2> n Math::BigInt::CODE(0x830c9f0)(/usr/share/perl5/Math/BigInt.pm:110): 110: '""' => sub { $_[0]->bstr(); },esta es la conversión necesaria del resultado a una cadena para ser impresa, continuamos ...
DB<2> a+y = 123456789246913578.123456789 Debugged program terminated. Use q to quit or R to restart, use O inhibit_exit to avoid stopping after program termination, h q, h R or h O to get additional info.
Si la sobrecarga fué especificada a través
de una referencia a subrutina se usa una llamada como subrutina mientras que si
se especificó como referencia simbólica se usa la sintáxis de método. Por ejemplo,
si $a
y $b
son dos objetos Math::BigFloat
, para las
declaraciones
package Math::BigFloat; use overload "*" => \&fmul, "+" => "fadd", "neg" => sub { Math::BigInt->new($_[0]->fneg()) };tendríamos los siguientes ejemplos de traducciones:
$a*$b |
Math::BigFloat::fmul($a, $b, "") |
$a+$b |
$a->fadd($b, "") |
-$a |
(sub { Math::BigFloat->new($_[0]->fneg()) })->($a, undef, "") |
La diferencia entre proporcionar una referencia a una subrutina o un nombre de método está relacionada con la herencia. Cuando se proporciona una referencia estamos asegurándonos de que se llama a la rutina con ese nombre, desactivando el mecanismo de búsqueda asociado con la herencia. Cuando proporcionamos un nombre de método, el operador sobrecargado va a llamar a ese método siguiendo los mecanismos de búsqueda asociados con la herencia (véase la sección 6.5).
Obsérvese que, en cualquier caso, la subrutina de implementación es llamada siempre con tres argumentos:
undef
si no existe)
flag
indicando cuando los operandos fueron intercambiados
flag
proviene del requerimiento de que el primer
argumento debe ser un objeto de la clase sobrecargada (en el ejemplo la clase Math::BigFloat
).
Si Perl detecta una expresión de la forma 4+$a
la traduce por
$a->fadd(4,1)
, donde el segundo argumento avisa de la inversión producida.
Es por esto que, para operaciones no conmutativas como la resta o la divisón, la función de implementación suele comenzar asi:
sub bsub { my ($op1, $op2, $reversed) = @_; ($op1, $op2) = ($op2, $op1) if $reversed; if (UNIVERSAL::isa($op1, 'Math::BigFloat') { ... # $op1 es un objeto } ... }Veamos otro ejemplo, en el que el objeto esta a la derecha y a la izquierda tenemos una constante:
$ cat ./bigfloat3.pl #!/usr/bin/perl -d use strict; use Math::BigFloat; my $a = Math::BigFloat->new('123_456_789_123_456_789'); print "123_456_789_123_456_789-a = ",'123_456_789_123_456_789'-$a,"\n";Al ejecutar, tenemos:
$ ./bigfloat3.pl main::(./bigfloat3.pl:5): my $a = Math::BigFloat->new('123_456_789_123_456_789'); DB<1> n main::(./bigfloat3.pl:7): print "123_456_789_123_456_789-a = ",'123_456_789_123_456_789'-$a,"\n"; DB<1> s Math::BigInt::CODE(0x82f997c)(/usr/share/perl5/Math/BigInt.pm:47): 47: '-' => sub { my $c = $_[0]->copy; $_[2] ? 48: $c->bneg()->badd($_[1]) : 49: $c->bsub( $_[1]) }, DB<1> p "@_" 123456789123456789 123_456_789_123_456_789 1Vemos como el tercer argumento esta a
1
y como la subrutina anónima
que trata el caso del menos binario convierte la expresión $a-$b
en (-$b)+$a
en el caso en que el objeto es el segundo término.
Continuamos la ejecución:
DB<2> c 123_456_789_123_456_789-a = 0 Debugged program terminated. Use q to quit or R to restart, use O inhibit_exit to avoid stopping after program termination, h q, h R or h O to get additional info.Observe que, en general se espera que los operadores sobrecargados puedan manejar la operación de números y objetos de la clase como en
4+$a
. Por tanto nuestro código debe manipular no solo
la operación de objetos sino también la de objetos y números. El ejemplo anterior del método
substract
, a través del uso del método UNIVERSAL::isa
(véase sección 6.5) muestra una forma de hacerlo.
Si ambos operandos son objetos sobrecargados pertenecientes a clases distintas se aplica el método correspondiente al primero:
DB<1> package A; use overload '+' => 'myadd'; sub myadd { 5 } DB<2> package B; use overload '+' => 'myadd'; sub myadd { 9 } DB<3> $x = bless {}, 'A' DB<4> $y = bless {}, 'B' DB<5> p $x+$y 5 DB<6> p $y+$x 9
El módulo overload.pm
asume las relaciones habituales entre operadores
y aprovecha este conocimiento. Así, si se da una implementación para el -
binario, el automáticamente sobrecargará el operador de asignación -=
,
los dos de decremento (- -
) y el -
unario (-$x = 0 - $x
).
Del mismo modo, si sobrecargamos el operador de comparación (<=>
), automáticamente
sobrecargamos los restantes operadores de comparación, ya que estos pueden ser deducidos
de aquél.
Esta sobrecarga automática se realiza si no se declara específicamente la sobrecarga del operador. En caso contrario se usará la definida por el programador.