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;
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:
@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.
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.
Los prototipos se construyen a partir de átomos prototipo los cuáles estan hechos de un prefijo o de un escape y un prefijo.
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.
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?
Un prototipo del tipo @
o del tipo %
fuerza un contexto de lista y consume el resto
de argumentos.
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 64Observe 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 *
fuerza una referencia a un typeglob.
Un prototipo de la forma ;
separa los argumentos requeridos
de los argumentos opcionales.
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 |
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?
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
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 en Scalar::Util permite establecer dinámicamente el prototipo de una función o borrarlo:
set_prototype CODEREF, PROTOTYPEEstablece el prototipo de
CODEREF
o lo borra si PROTOTYPE
es undef
.
Returna CODEREF
. Ejemplo:
set_prototype \&foo, ’$$’;
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.