Prototipos

Los prototipos en Perl son un mecanismo que permite

Introducción: El Problema

Como ejemplo, supongamos que queremos escribir una función shift2 que retorna y elimina los dos primeros elementos del principio de un array. Nos gustaría usarla como shift:

@a = 1..10;
($f, $s) = shift2 @a;

Si queremos escribirla, no vale hacer:

sub shift2 { splice @_, 0, 2 }

Observe el comportamiento del siguiente programa.

$ cat -n shift2.pl
     1  #!/usr/bin/perl -w
     2  use strict;
     3
     4  sub shift2 { splice @_, 0, 2 }
     5
     6  my @a = 1..5;
     7  my ($f, $g) = shift2 @a;
     8
     9  local $" = ',';
    10  print "f = $f, g = $g  \@a = (@a)\n";
$ ./shift2.pl
f = 1, g = 2  @a = (1,2,3,4,5)
La llamada a splice en la línea 4 retira Los elementos de @_ pero no del array @a.

La solución al problema es pasar el array por referencia:

sub shift2 { splice @{$_[0]}, 0, 2 }
pero entonces la llamada queda distinta, obligándonos a pasar la referencia:

@a = 1..10;
($f, $s) = shift2 \@a;

Control de la LLamada Mediante Prototipos

Si queremos que la interfaz de shift2 sea similar a la de shift debemos hacer uso de prototipos. Podemos redeclarar la función como:

sub shift2(\@) { splice @{$_[0]}, 0, 2 }

El prototipo \@ indica dos cosas:

Las llamadas siguientes producen error:
@x = shift2 %a
Type of arg 1 to main::shift2 must be array (not hash dereference) 

@x = shift2 \@a
Type of arg 1 to main::shift2 must be array

@x = shift2 @a, @b
Too many arguments for main::shift2

Como se ve en el ejemplo, se comprueba que el número y la clase de los parametros coinciden.

LLamada con Ampersand y Prototipos

Para que los prototipos trabajen de esta forma la llamada a la función debe hacerse sin prefijarse de &. Vea el siguiente ejemplo:

$ cat -n ./shift2.pl
 1  #!/usr/bin/perl -w
 2  use strict;
 3
 4  sub shift2(\@) { my $arr = shift; splice @$arr, 0, 2 }
 5
 6  my @a = 1..6;
 7  my ($f, $g) = shift2 @a;
 8
 9  local $" = ',';
10  print "f = $f, g = $g  \@a = (@a)\n";
11
12  ($f, $g) = shift2(@a);
13  print "f = $f, g = $g  \@a = (@a)\n";
14
15  # dará error
16  ($f, $g) = &shift2(@a);
17  print "f = $f, g = $g  \@a = (@a)\n";
$ ./shift2.pl
f = 1, g = 2  @a = (3,4,5,6)
f = 3, g = 4  @a = (5,6)
Can't use string ("5") as an ARRAY ref while "strict refs" in use at ./shift2.pl line 4.
La llamada de la línea 16 produce un error: lo queda de @a es enviado como parámetro a la rutina y se produce la protesta.

Formato de los Prototipos

Los prototipos se construyen a partir de átomos prototipo los cuáles estan hechos de un prefijo o de un escape y un prefijo.

Prototipo Referencia a un Escalar

Un prototipo como \$ le indica a Perl que en la llamada se debe especificar una variable escalar la cuál será convertida en la rutina en una referencia a la variable. Véase la sesión que sigue:

lhp@nereida:~/Lperl/src$ perl -wde 0
main::(-e:1):   0
  DB<1> sub r(\$) { print "@_\n" }
  DB<2> r qw{a b c}
Type of arg 1 to main::r must be scalar (not list) at (eval 6)
  DB<3> r "hola"
Type of arg 1 to main::r must be scalar (not constant item) at (eval 7)
  DB<4> $x = 4
  DB<5> r $x
SCALAR(0x842f57c)
  DB<6> r \$x
Type of arg 1 to main::r must be scalar (not single ref constructor) at (eval 10)
  DB<7> r $x, qw{a b c}
Too many arguments for main::r at (eval 11)
  DB<8> r $x, 1..3
Too many arguments for main::r at (eval 12)

Cualquier prototipo backslash (\) representa a un argumento que debe comenzar con ese carácter. De ahí las protestas del intérprete en las líneas 2, 3 y 6.

El Prototipo $

Un prototipo de la forma $ lo que hace es que fuerza un contexto escalar. Observe la conducta del siguiente programa:

$ cat -n ./dollarproto.pl
     1  #!/usr/bin/perl -w
     2  use strict;
     3
     4  sub t ($@) { my $a = shift; my @b = @_; print "a = $a, b = (@b)\n"; }
     5
     6  my ($a, $b, $c) = qw/uno dos tres/;
     7  t ':',$a, $b, $c;
     8
     9  my @r = 1..5;
    10  t @r;
    11
$ ./dollarproto.pl
a = :, b = (uno dos tres)
a = 5, b = ()
¿Podría explicar la salida?

Los Prototipos @ y %

Un prototipo del tipo @ o del tipo % fuerza un contexto de lista y consume el resto de argumentos.

El Prototipo &

Un prototipo de la forma & fuerza una referencia a código. Si se trata del primer argumento, entonces la palabra sub es opcional y se puede omitir en la llamada permitiendo un estilo similar al uso de los operadores sort, eval o grep:

$ cat -n ampproto.pl
     1  #!/usr/bin/perl -w
     2  use strict;
     3
     4  sub t (&$) { my ($f, $x) = @_; print &$f($x),"\n" }
     5
     6  t { $_[0]**3 } 4;
     7
$ ./ampproto.pl
64
Observe la ausencia de coma en la llamada de la línea 6. Esta característica nos permite escribir funciones que siguen una sintáxis similar a la de sort.

El Prototipo *

El prototipo * fuerza una referencia a un typeglob.

Argumentos Requeridos y Argumentos Opcionales

Un prototipo de la forma ; separa los argumentos requeridos de los argumentos opcionales.

Ejemplos

Veamos otros ejemplos de declaraciones de prototipos de operadores ya existentes en Perl:



myreverse (@) myreverse $a,$b,$c
myjoin ($@) myjoin ":",$a,$b,$c
mypop (\@) mypop @array
mysplice (\@$$@) mysplice @array,@array,0,@pushme
mykeys (\%) mykeys %{$hashref}
myopen (*;$) myopen HANDLE, $name
mypipe (**) mypipe READHANDLE, WRITEHANDLE
mygrep (&@) mygrep { /foo/ } $a,$b,$c
myrand ($) myrand 42
mytime () mytime

Ejercicio 4.16.1   Suponga que quiere escribir una función mypush que actúa exactamente como lo hace push: La llamada push @a, 4, 5, 6 empuja los elementos de la lista (4, 5, 6) en @a. ¿Como debería ser el prototipado de dicha subrutina?

Peligros en el Uso de Prototipos

El operador mkdir tiene un prototipo $;$. Véanse las consecuencias de semejante prototipo:

lhp@nereida:/tmp$ perl -wde 0
main::(-e:1):   0
  DB<1> @a = ('tutu', '077')
  DB<2> x mkdir @a
0  1
  DB<3> !!ls -ltr | tail -1
drwxr-xr-x 2 lhp      lhp        4096 2008-04-16 14:05 2
  DB<4> x &mkdir @a
syntax error at (eval 7)[/usr/share/perl/5.8/perl5db.pl:628] line 2, near "&mkdir @a"
  DB<5> x mkdir $a[0], $a[1]
0  1
  DB<6> !!ls -ltr | tail -2
drwxr-xr-x 2 lhp      lhp        4096 2008-04-16 14:05 2
d--x--xr-x 2 lhp      lhp        4096 2008-04-16 14:07 tutu

Ejercicio 4.16.2   ¿Podría explicar el resultado?

La función prototype

La función prototype retorna una cadena describiendo el prototipo de la referencia a la función pasada como argumento:

lhp@nereida:~/Lperl/src$ perl -de 0
  DB<1> sub a($$)
  DB<2> p prototype \&a
$$
  DB<3> x prototype 'CORE::mkdir'
  0  '$;$'
Si el argumento es una cadena que comienza por CORE:: se interpreta como el nombre de un operador Perl. Si el operador no se puede sobreescribir o sus argumentos no se pueden expresar como un prototipo retorna undef.

La función set_prototype

La función set_prototype en Scalar::Util permite establecer dinámicamente el prototipo de una función o borrarlo:

             set_prototype CODEREF, PROTOTYPE
Establece el prototipo de CODEREF o lo borra si PROTOTYPE es undef. Returna CODEREF. Ejemplo:

               set_prototype \&foo, ’$$’;

Reescribiendo reduce mediante Prototipos

El siguiente código implanta una función reduce similar a la proveída por el módulo List::Util presentado en la sección 1.13.2. Aunque la versión en List::Util es esencialmente similar en funcionalidad, es mas eficiente al estar escrita en C.

lhp@nereida:~/Lperl/src/hop/Chap7$ cat -n reduce.pl
 1  #!/usr/bin/perl -w
 2  use strict;
 3  use Scalar::Util qw(looks_like_number);
 4  use List::MoreUtils qw(all);
 5
 6  sub reduce (&@) {
 7    my $code = shift;
 8    my $val = shift;
 9    local ($a, $b);
10    for (@_) {
11      ($a, $b) = ($val, $_);
12      $val = $code->()
13    }
14    return $val;
15  }
16
17    die "\n$0 number number ...\n"
18  unless @ARGV and all { looks_like_number($_) } @ARGV;
19  my $s = reduce { $a+$b } @ARGV;
20  my $p = reduce { $a*$b } @ARGV;
21
22  print "Suma = $s, Prod = $p\n";

El uso del prototipo nos permite omitir el uso de la palabra reservada sub y la coma intermedia. La localización de las variables $a y $b nos permite evitar la posible contaminación de variables globales con el mismo nombre.



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