[caracas-pm] [P] Sobre pragma attributes, su funcionamiento y utilidad

Ernesto Hernández-Novich emhnemhn at gmail.com
Sun Jan 10 18:19:13 PST 2010


Originalmente envié esta respuesta el 2009-12-27, pero aparentemente no llegó
a pesar de que mis bitácoras confirman que el mensaje llegó hasta pm.org

On Sat, 2009-12-19 at 11:31 +0000, RrodolfoS . wrote:
> Buenos días a todos,
> 
> En lo últimos días he estado revisando Catalyst y realizando una pausa
> con cada duda que se me presenta al recorrer el tutorial, no logro
> entender el funcionamiento de los atributos en la definición de las
> acciones por cada controlador, espero ser claro, la duda es con
> relación al pragma attributes, no encuentro documentación clara al
> respecto, como funciona, de que forma se usan esos atributos, según
> (http://perldoc.perl.org/attributes.html), le cambia la semántica al
> método o a la variable, pero no logro crear un uso práctico, en
> definitiva no entiendo el citado pragma, si alguien puede dar un
> ejemplo, o remitirme a alguna documentación mas sencilla se lo
> agradezco.

Originalmente, sólo había tres tipos de atributos para subrutinas en
Perl: method, lvalue y locked.

El atributo 'lvalue' permite que una función retorne una dirección
asignable (l-value) en lugar de un valor (r-value). En una asignación,
los elementos de la izquierda (l-values) interesan por su ubicación
("guardar aquí") mientras que los elementos de la derecha (r-values)
interesan por su valor ("guardar esto"). Las subrutinas siempre son
r-values a menos que se utilice el atributo lvalue, i.e.

use feature qw(say)
my @arreglo;
sub posicion : lvalue {
  my $p = shift;
  $arreglo[ $p ]     # Se retorna la ubicación y no el valor.
}
for (0..10) {
  posicion( $_ ) = $_;
}
say $_ foreach (@arreglo);

El atributo 'locked' es importante si el programa es multihilo, pues
garantiza que uno y sólo un hilo puede ingresar a la subrutina al mismo
tiempo (lo convierte en una "región crítica"). Si además tiene el
atributo 'method', se establecerá un bloqueo sobre el primer argumento
pasado a la función (que usualmente representa el objeto sobre el cual
se trabaja y que no quisieras sea accedido concurrentemente).

Partiendo de esta idea, se generalizó el concepto de atributos sobre
subrutinas de manera que el programador pueda poner cualquier cantidad
de atributos separados por comas. Un atributo es _cualquier_
identificador (letras, dígitos y guión bajo), y puede tener una lista de
cosas entre paréntesis (el compilador verifica que los paréntesis estén
balanceados). Es importante notar que Perl no realiza actividades de
análisis sintáctico ni semántico sobre el atributo, sino que lo toma
"tal cual" y lo inyecta en el módulo siguiendo una estrategia simple.

Supongamos que declaras

package Grok;
use attributes;
...
sub foo : bar(baz,qux), bleh { ... }

luego, en _cualquier_ rutina dentro del package Grok puedes ejecutar

my @attlist = attributes::get( \&foo )

y tendrás @attlist = qw( bar(baz,qux) bleh ). Qué hacer con esa
información es problema del programador, incluyendo el análisis de cada
elemento de la lista y su posible "aplicación".

Para simplificar estas tareas existe el módulo Attribute::Handlers, que
para el mismo ejemplo artificial se usaría

package Grok;
use Attribute::Handlers;
no warnings 'redefine';
sub foo : bar(baz,qux), bleh { ... }

pero le puedes agregar

sub bar : ATTR(CODE) {
  # Esta función se ejecutará durante la _compilación_ para
  # _cada_ función que tenga atributo 'bar'. En este ejemplo,
  # se ejecutará cuando se compila 'foo'.
  my ($package,$sym,$coderef,$codename,$data,$phase,$file,$line) = @_;
  ...
}

Al compilar 'foo' en nuestro ejemplo, se invocaría 'bar' recibiendo:

$package = 'Grok'
$sym = *foo  (el typeglob)
$coderef = \&foo (apuntador al código de la rutina)
$codename = 'foo'
$data = referencia a un arreglo que tiene 'baz' y 'qux' como elementos
$phase de la compilación (BEGIN, CHECK, INIT o END).
Nombre ($file) y línea ($line) del archivo donde está la rutina.

Entonces, todo lo que resta es que el programador de la clase decida qué
hay que hacer, a tiempo de compilación, con la rutina a la cual se le
puso algún atributo. Entre la cantidad de cosas que se pueden hacer,
está analizar los atributos, _generar_ una nueva función que "envuelva"
al coderef de la función original, y reinstalarla en el nombre original
(con el mismo estilo de lo que hace Memoize). Por ejemplo, imagina un
atributo 'Logger' que debe "hacer que cada vez que se ingrese a la
rutina se escriba un mensaje en una bitácora", entonces dentro del
manejador podría escribir algo como

sub Logger : ATTR(CODE) {
  my ($package,$sym,$coderef,$codename,$data,$phase,$file,$line) = @_;
  # Verificaciones y quien sabe qué otra cosa ...
  my $newfunc = sub {
    log_somewhere("Ingresando $codename");
    $coderef->( @_ );  # La invocación "original"
    log_somewhere("Terminando $codename");
  }
  ${$sym}{CODE} = $newfunc;
}

Muy resumido: construyo una función anónima que hace el log, invoca a la
función original pasando los argumentos que se pasarían a la función
cuando fuese invocada y vuelve a hacer el log. Luego, tomo esa función y
la instalo en la tabla de símbolos original para que cuando llamen a la
función original, en realidad llamen a mi función "envoltorio" de la
función original.

Catalyst saca provecho de esto de manera similar: cuando le pones
atributos a los métodos de _tu_ clase, a tiempo de compilación son
manipuladas por los métodos provistos por Catalyst para envolverlas en
funciones adecuadas. Esas funciones envoltorio están parametrizadas de
acuerdo a las cosas que permite Catalyst en los atributos.

Entonces, los atributos para subrutinas son _cadenas_, que han de ser
procesadas a tiempo de _compilación_ por rutinas escritas por el
_programador_ con el propósito de alterar "algo" en las funciones
originales. Otra manera de verlo

sub original : alterala(con,estos,parámetros) { ... }

quiere decir que al compilar 'original' habrá que invocar a la rutina
'alterala' para que "haga alguna alteración" sobre el código de
'original' usando la lista de parámetros ('con', 'estos', 'parámetros').
¿Cuál alteración? La que esté implantada en 'alterala' y que puede sacar
provecho de toda la flexibilidad de typeglobs, coderefs, clausuras y
modificación dinámica de código (como usar B::Deparse y otras hierbas).

Si, no es fácil de entender a la primera.
    
-- 
Ernesto Hernández-Novich - Linux 2.6.28 i686 - Unix: Live free or die!
Geek by nature, Linux by choice, Debian of course.
If you can't aptitude it, it isn't useful or doesn't exist.
GPG Key Fingerprint = 438C 49A2 A8C7 E7D7 1500 C507 96D6 A3D6 2F4C 85E3




More information about the caracas-pm mailing list