The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

perlreftut - Tutorial muy breve de Mark sobre las referencias *** DOCUMENTO SIN REVISAR ***

DESCRIPCIÓN

Una de las nuevas características más importantes en Perl 5 era la capacidad para gestionar complicadas estructuras de datos como arrays multidimensionales y hashes anidados. Para habilitarlas, Perl 5 introdujo una característica llamada referencias, y utilizando las referencias es la clave para gestionar complicadas estructuras de datos en Perl. Desafortunadamente, hay que aprender mucha sintaxis graciosa, y la página del manual principal puede ser dura de seguir. El manual es bastante completo, y a veces las personas encuentran que eso es un problema, porque puede ser difícil decir qué es lo importante y qué no.

Afortunadamente, sólo necesita saber un 10 % de lo que hay en la página principal para obtener el 90 % de los beneficios. Este documento le mostrará ese 10 %.

¿Quién necesita complicadas estructuras de datos?

Un problema que siempre aparece cuando se necesita un hash cuyos valores son listas. Perl tiene hashes, naturalmente, pero los valores tienen que ser escalares; no pueden ser listas.

¿Por qué querría un hash de listas? Tomemos un ejemplo sencillo: usted tiene un archivo de nombres de ciudad y de países, así:

        Chicago, USA
        Frankfurt, Germany
        Berlin, Germany
        Washington, USA
        Helsinki, Finland
        New York, USA

y quiere producir una salida como esta, con cada país se menciona una vez, seguido de una lista alfabética de las ciudades de ese país:

        Finland: Helsinki.
        Germany: Berlin, Frankfurt.
        USA:  Chicago, New York, Washington.

Para hacer esto, la forma natural es tener un hash cuyas claves son nombres de países. Asociado con cada clave de nombre de país hay una lista de las ciudades de ese país. Cada vez que lea una línea desde la entrada, divídala en un país y una ciudad; busque por la lista de ciudades asociadas a ese país; y añada la nueva ciudad a esa lista. Cuando haya terminado de leer, itere sobre el hash de forma normal, ordenando cada lista de ciudades antes de imprimirla.

Si los valores de los hash no fueran listas, estaría perdido. Quizás podría combinar, de algún modo, todas las ciudades en una única cadena, y cuando llegue el momento de escribir la salida, tendría que romper la cadena en una lista, ordenar la lista, y volverla a convertir en una cadena. Esto es un jaleo y propenso a errores. Y frustante, porque Perl ya tiene un buena gestión de listas que podría resolver el problema con tan solo usarlas.

La solución

Durante el momento en que Perl 5 apareció, estábamos convencidos de esta decisión de diseño: los valores Hash deben ser escalares. La solución son las referencias.

Una referencia es un valor escalar que se refiere a un array entero o un hash entero (o cualquier otra cosa). Los Nombres son una clase de referencias con las que ya está familiarizado. Piense en el Presidente de los Estados Unidos: es una inapropiada bolsa de sangre y huesos, mezclados. Pero cuando hablas sobre él, o para representarlo en un programa de ordenador, todo lo que necesitas es la fácil y conveniente cadena escalar "Barack Obama".

Las referencias en Perl son como los nombres para los arrays y los hashes. Son nombres internos y privados de Perl, así puede estar seguro de que son inequívocos. A diferencia de "Barack Obama", una referencia solo refiere a una cosa, y siempre sabe a lo que refiere. Si tiene una referencia a un array, puede recuperar el array entero, a partir de ella. Si tiene una referencia a un hash, puede recuperar el hash entero. Pero la referencia sigue siendo un sencillo y compacto valor escalar.

No puede tener un hash cuyos valores sean arrays; los valores hash solo pueden ser escalares. Aquí nos quedamos bloqueados. Pero una única referencia puede referirse a un array entero, y las referencias son escalares, así que puede tener un hash de referencias a arrays, y actuará como un hash de arrays, y será tan útil como un hash de arrays.

Volveremos a este problema de ciudades-países más tarde, después de que veamos algo de la sintaxis para gestionar referencias.

Sintaxis

Hay justo dos maneras para hacer una referencia, y justo dos maneras de usarla.

Crear referencias

Regla de creación 1

Si pone una \ delante de una variable, obtiene una referencia a esa variable.

    $aref = \@array;         # $aref ahora almacena una referencia a @array
    $href = \%hash;          # $href ahora almacena una referencia a %hash
    $sref = \$scalar;        # $sref ahora almacena una referencia a $scalar

Una vez que la referencia está almacenada en una variable como $aref o $href, puede copiarla o almacenarla de la misma forma que cualquier otro valor escalar:

    $xy = $aref;             # $xy ahora almacena una referencia a @array
    $p[3] = $href;           # $p[3] ahora almacena una referencia a %hash
    $z = $p[3];              # $z ahora almacena una referencia a %hash

Estos ejemplos muestran cómo crear referencias a variables usando nombres. Algunas veces querrá crear un array o un hash que no tienen nombre. Esto es análogo a la forma en la que quiere usar la cadena "\n" o el número 80 sin tener que almacenarlo primero en una variable con nombre.

Regla de creación 2

[ ELEMENTOS ] crea un nuevo y anónimo array, y devuelve una referencia a ese array. { ELEMENTOS } crea un nuevo y anónimo hash, y devuelve una referencia a ese hash.

    $aref = [ 1, "foo", undef, 13 ];
    # $aref ahora almacena una referencia a un array

    $href = { APR => 4, AUG => 8 };
    # $href ahora almacena una referencia a un hash

Las referencias que obtiene de la regla 2 son de la misma clase de referencias que obtiene de la regka 1:

        # Esto:
        $aref = [ 1, 2, 3 ];

        # hace lo mismo que esto:
        @array = (1, 2, 3);
        $aref = \@array;

La primera línea es una abreviatura de las dos líneas siguientes, excepto que no crea una variable array superflua @array.

Si solo escribe [], obtiene un nuevo y vacío array anónimo. Si solo escribe {}, obtiene un nuevo y vacío hash anónimo.

Usar referencias

¿Qué puede hacer con una referencia una vez que la tiene? Es un valor escalar, y hemos visto que puede almacenarse como un escalar y recuperarlo como cualquier otro escalar. Hay otras dos formas de usarlas:

Regla de uso 1

Siempre puede usar una referencia de array, entre llaves, en lugar del nombre de un array. Por ejemplo, @{$aref} en lugar de @array.

A continuación, algunos ejemplos de cómo usarlos:

Arrays:

        @a              @{$aref}                Un array
        reverse @a      reverse @{$aref}        Invertir el array
        $a[3]           ${$aref}[3]             Un elemento del array
        $a[3] = 17;     ${$aref}[3] = 17        Asignando un elemento

En cada línea hay dos expresiones que hacen lo mismo. Las versiones a la izquierda operan sobre el array @a. Las versiones a la derecha operan sobre el array que está referenciado por $aref. Una vez que encuentran el array sobre el que hay que operar, ambas versiones hacen las mismas operaciones sobre los arrays.

Usar una referencia hash es exactamente lo mismo:

        %h              %{$href}              Un hash
        keys %h         keys %{$href}         Obtiene las claves del hash
        $h{'red'}       ${$href}{'red'}       Un elemento del hash
        $h{'red'} = 17  ${$href}{'red'} = 17  Asignando un elemento

Cualquier cosa que quiera hacer con una referencia, Regla de uso 1 le dice cómo hacerlo. Simplemente escriba el código Perl que tendría que escribir para hacer lo mismo que con un array o hash normales, y entonces reemplazar el nombre del array o hash con {$reference}. "¿Cómo hago un bucle sobre un array cuando todo lo que tengo es una referencia?" Bueno, para hacer un bucle sobre un array, escribiría

        for my $elemento (@array) {
          ...
        }

así que reemplace el nombre del array, @array, con la referencia:

        for my $elemento (@{$aref}) {
          ...
        }

"¿Cómo imprimo los contenidos de un hash cuando todo lo que tengo es una referencia?" Primero escriba el código para imprimir un hash:

        for my $clave (keys %hash) {
          print "$clave => $hash{$clave}\n";
        }

Y ahora reemplace el nombre del hash con la referencia:

        for my $clave (keys %{$href}) {
          print "$clave => ${$href}{$clave}\n";
        }

Regla de uso 2

La Regla de uso 1 es todo lo que realmente necesita, porque le dice cómo hacer absolutamente todo lo que necesita hacer con las referencias. Pero lo más común que hay que hacer con un array o un hash es extraer un único elemento, y la notación de la Regla de uso 1 es algo pesada. Así que aquí hay una abreviatura.

${$aref}[3] es difícil de leer, así que puede escribirlo de otra forma: $aref->[3].

${$href}{red} es difícil de leer, así que puede escribirlo de otra forma $href->{red}.

Si $aref almacena una referencia a un array, entonces $aref->[3] es el cuarto elemento de un array. No lo confunda con $aref[3], que es el cuarto elemento de un array totalmente diferente, aparentemente llamado @aref. $aref y @aref no están relacionados, de la misma manera que tampoco lo están $item e @item.

De forma similar, $href->{'red'} es parte del hash referenciado por la variable escalar $href, quizás incluso un hash que no tiene nombre. $href{'red'} es parte de hash aparentemente llamado %href. Es fácil olvidarse el ->, y si lo hace, obtendrá valores extraños cuando su programa obtenga elementos de array y hash de unos arrays y hashes inexperados que no son los que quiere usar.

Un ejemplo

Veamos un ejemplo rápido de la utilidad de todo esto.

Primero, recuerde que [1, 2, 3] crea un array anónimo que contiene (1, 2, 3), y le da una referencia a ese array.

Ahora fíjese en esto

        @a = ( [1, 2, 3],
               [4, 5, 6],
               [7, 8, 9]
             );

@a es un array con tres elementos, y cada uno es una referencia a otro array.

$a[1] es una de esas referencias. Se refiere a un array, que contiene (4, 5, 6), y por que es una referencia a un array, Regla de uso 2 dice que puede escribirse como $a[1]->[2] para obtener el tercer elemento de ese array. $a[1]->[2] es el 6. De modo parecido, $a[0]->[1] es el 2. Lo que tenemos aquí es un array bidimensional, puede escribir $a[FILA]->[COLUMNA] para obtener o definir el elemento en una fila y columna del array.

La notación sigue pareciendo un poco pesada, así que aquí hay una abreviatura más:

Regla de la fecha

Entre dos subíndices, la flecha es opcional.

En lugar de $a[1]->[2], puede escribirlo así $a[1][2]; significa lo mismo. En lugar de $a[0]->[1] = 23, puede escribirlo así $a[0][1] = 23; significa lo mismo.

¡Ahora ya se parece a un array bidimensional!

Puede ver por qué las flechas son importantes. Sin ellas, tendríamos que escribir ${$a[1]}[2] en lugar de $a[1][2]. Para arrys tridimensionales, nos permiten escribir $x[2][3][5] en lugar del ilegible ${${$x[2]}[3]}[5].

Solución

Aquí está la respuesta al problema que hemos mostrado antes, de reformatear un archivo de nombres de ciudades y países.

    1   my %tabla;

    2   while (<>) {
    3     chomp;
    4     my ($ciudad, $pais) = split /, /;
    5     $tabla{$pais} = [] unless exists $tabla{$pais};
    6     push @{$tabla{$pais}}, $ciudad;
    7   }

    8   for my $pais (sort keys %tabla) {
    9     print "$pais: ";
   10     my @ciudades = @{$tabla{$pais}};
   11     print join ', ', sort @ciudades;
   12     print ".\n";
   13   }

Este programa tiene dos partes: las líneas 2 a 7 leen la entrada y generan una estructura de datos, y las líneas 8 a 13 analizan los datos e imprimen el informe. Vamos a tener un hash, %tabla, cuyas claves son nombre de países, y cuyos valores son referencias a arrays de nombres de ciudades. La estructura de datos se parecerá a esto:

           %tabla
        +-------+---+
        |       |   |   +-----------+--------+
        |Germany| *---->| Frankfurt | Berlin |
        |       |   |   +-----------+--------+
        +-------+---+
        |       |   |   +----------+
        |Finland| *---->| Helsinki |
        |       |   |   +----------+
        +-------+---+
        |       |   |   +---------+------------+----------+
        |  USA  | *---->| Chicago | Washington | New York |
        |       |   |   +---------+------------+----------+
        +-------+---+

Primero miraremos la salida. Suponiendo que ya tenemos esta estructura, ¿cómo la imprimimos?

    8   for my $pais (sort keys %tabla) {
    9     print "$pais: ";
   10     my @ciudades = @{$tabla{$pais}};
   11     print join ', ', sort @ciudades;
   12     print ".\n";
   13   }

%tabla es un hash normal, y obtenemos de él una lista de claves, luego las ordenamos, y luego las recorremos, de la forma usual. El único uso de las referencias es en la línea 10. $tabla{$pais} busca por la clave $pais en el hash y obtiene el valor, que es una referencia a un array de ciudades en ese país. Regla de uso 1 dice que podemos recuperar el array diciendo @{$tabla{$pais}}. La línea 10 es algo como esto

        @ciudades = @array;

excepto que el nombre array se reemplaza por la referencia {$tabla{$pais}}. El @ le indica a Perl que obtenga el array entero. Habiendo obtenido la lista de ciudades, la ordenamos, la unimos, y la imprimimos de la forma usual.

Líneas 2 a 7 son responsables para la generación de la estructura, al principio del programa. Aquí están de nuevo:

    2   while (<>) {
    3     chomp;
    4     my ($ciudad, $pais) = split /, /;
    5     $tabla{$pais} = [] unless exists $tabla{$pais};
    6     push @{$tabla{$pais}}, $ciudad;
    7   }

Líneas 2 a 4 adquieren los nombres de un ciudad y un país. Línea 5 comprueba si el país ya está presente como clave en el hash. Si no, el programa usa la notación [] (Regla de creación 2) para manufacturar un nuevo array de ciudades, vacío y anónimo, e instala una referencia a él en el hash bajo la clave apropiada.

Línea 6 instala el nombre de la ciudad en el array apropiado. $tabla{$pais} almacena una referencia al array de ciudades que hemos visto que acompañan a ese país. La línea 6 es exactamente como

        push @array, $ciudad;

excepto que el nombre array se reemplaza por la referencia {$tabla{$pais}}. El push añade un nombre de ciudad al final del array referenciado.

Hay un fino detalle que nos hemos saltado. La línea 5 es innecesaria, y podemos librarnos de ella.

    2   while (<>) {
    3     chomp;
    4     my ($ciudad, $pais) = split /, /;
    5   ####  $tabla{$pais} = [] unless exists $tabla{$pais};
    6     push @{$tabla{$pais}}, $ciudad;
    7   }

Si ya existe una entrada en %tabla para el $pais actual, entonces no se diferencia en nada a la versión anterior. La línea 6 localizará el valor en $tabla{$pais}, que es una referencia a un array, y mete $ciudad en el array. Pero, ¿qué hace cuando $pais almacena una clave, por ejemplo Grecia, y todavía no está en %tabla?

Esto es Perl, así que hace lo correcto. Él ve que usted quiere meter Atenas en un array que no existe, así que él crea por usted un nuevo, vacío y anónimo array, lo instala en %tabla, y entonces mete Atenas dentro. Esto se llama autovivificación: traer cosas a la vida, automáticamente. Perl vio que la clave no estaba en el hash, así que creó una nueva entrada hash de forma automática. Perl vio que quería usar el valor del hash como un array, así que creó un nuevo array vacío y luego creó una referencia a él, en el hash, también de forma automática. Y, como es normal, Perl hizo que el array fuera un elemento más largo, para acomodar el nuevo nombre de ciudad.

El resto

Le prometimos darle el 90 % de los beneficios con el 10 % de los detalles, y eso significa que hemos dejado fuera el 90 % de los detalles. Ahora que tiene una visión general de las partes importantes, debería ser más sencillo leer la página de manual perlref, que muestra el 100 % de los detalles.

Algunos de los puntos destacados de perlref:

  • Puede crear referencias a cualquier cosa, incluyendo escalares, funciones y otras referencias.

  • En la Regla de uso 1, puede omitir las llaves siempre que lo que haya en su interior es una variable escalar atómica, como $aref. Por ejemplo, @$aref es lo mismo que @{$aref}, y $$aref[1] es lo mismo que ${$aref}[1]. Si usted está empezando, quizás quiera adoptar el hábito de incluir siempre las llaves.

  • Esto no copia el array subyacente:

            $aref2 = $aref1;

    Obtiene dos referencias al mismo array. Si modifica $aref1->[23] y luego mira en $aref2->[23], verá el cambio.

    Para copiar el array, use

            $aref2 = [@{$aref1}];

    Esto utiliza la notación [...] para crear un nuevo array anónimo, y al $aref2 se le asigna una referencia al nuevo array. El nuevo array se inicializa con los contenidos de array referenciado por $aref1.

    De forma similar, para copiar un hash anónimo, puede usar

            $href2 = {%{$href1}};
  • Para ver si una variable contiene una referencia, use la función ref. Devuelve verdadero si su argumento es una referencia. De hecho, es un poco mejor que eso: devuelve HASH para las referencias a hash y ARRAY para las referencias a array.

  • Si intenta usar una referencia como una cadena, obtiene cadenas como

            ARRAY(0x80f5dec)   o     HASH(0x826afc0)

    Si nunca ha visto una cadena que se parezca a estas, sabrá que no ha impreso una referencia por error.

    Un efecto lateral de esta representación es que puede usar eq para ver si dos referencias refieren a lo mismo. (Pero debería usar de forma más usual == en su lugar porque es mucho más rápido).

  • Puede usar una cadena como si fuera una referencia. Si usa la cadena "foo" como una referencia a un array, se considera que es una referencia al array @foo. A esto se le llama una referencia simbólica. La declaración use strict 'refs' desactiva esta característica, que puede causar toda suerte de problemas si la usa por accidente.

Podría preferir ir a perllol en lugar de perlref; allí se habla en detalle de listas de listas y de arrays multidimensionales. Después de eso, debería ir hacia perldsc; es el recetario de las Estructuras de datos, que muestra recetas para usar e imprimir arrays de hashes, hashes de arrays, y otras clases de datos.

Resumen

Todo el mundo necesita componen estructuras de datos, y en Perl la forma de hacerlo es con las referencias. Hay cuatro reglas importantes para gestionar referencias: dos para crearlas y dos para usarlas. Una vez que conoce estas reglas puede hacer la mayor parte de las cosas importantes que necesita hacer con referencias.

Créditos

Autor: Mark Jason Dominus, Plover Systems (mjd-perl-ref+@plover.com)

Este artículo apareció originalmente en The Perl Journal ( http://www.tpj.com/ ) volumen 3, n.º 2. Reproducido con permiso.

El título original era Understand References Today (Entendiendo las referencias).

Condiciones de distribución

Copyright 1998 The Perl Journal.

Esta documentación es libre; puede redistribuirla o modificarla en los mismos términos que Perl.

Independientemente de su distribución, todos los ejemplos de código de estos archivos se quedan por la presente en el dominio público. Se le permite y anima a utilizar este código en sus propios programas por simple diversión o con fines de lucro, como mejor le parezca. Un simple comentario en el código dándonos crédito sería cortés, pero no es necesario.

TRADUCTORES

  • Joaquín Ferrero (Tech Lead)

  • Enrique Nell (Language Lead)