Sobrecarga de Operadores

Que es la Sobrecarga

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.123456789
y queremos que el módulo, como ilustra el ejemplo, sobrecargue las operaciones binarias y unarias usuales así como el uso de las constantes.

El Módulo overload

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.

Las Claves

La clave puede ser cualquiera de las reseñadas en la tabla 6.8.


Tabla: Operadores que pueden ser sobrecargados en Perl. neg es la negación unaria
Categoría Operadores / Claves
Aritmética + - * / % ** x . neg
Bit << >> & | ^ ~
Asignación += -= *= /= %= **= <<= >>= x= .= ++ --
Comparación < <= > >= == != <=> lt le gt ge eq ne cmp
Funciones atan cos sin exp abs log sqrt
Conversiones q("") 0+ bool
Seudo-operadores nomethod fallback =


Los Valores

La referencia a la subrutina puede ser

¿Cuándo es LLamado el Manejador?

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.

Una Sesión con el Depurador

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> s
Hemos 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.123456789
Observe 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.

Formas de LLamada

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, "")

Métodos versus Subrutinas

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).

Los Tres Argumentos del Manejador

Obsérvese que, en cualquier caso, la subrutina de implementación es llamada siempre con tres argumentos:

  1. El primer operando
  2. El segundo operando (undef si no existe)
  3. Un flag indicando cuando los operandos fueron intercambiados
La necesidad del 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.

Manejadores de Operaciones no Commutativas

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 1
Vemos 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.

Conflictos en la Sobrecarga

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

Propagación de la Sobrecarga

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.



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