Aprende Perl en 2 horas y 30 minutos

Por Sam Hughes

Perl es un lenguage interpretado de alto nivel y dinámicamente tipado comparable con PHP y Python. Perl debe gran parte de su sintaxis a herramientas antiguas de shell scripting (guiones de órdenes) y es famoso por su sobreuso de símbolos confusos, que hacen una búsqueda de Google sobre ellos casi imposible. La herencia shell scripting de Perl lo hace beneficioso para escribir código pegamento: scripts que enlazan otros scripts y programas. Perl es altamente adecuado para el procesamiento de datos textuales y su produción. Perl es usado extensamente, popular, altamente portable y con mucho soporte. Perl fue diseñado con la filosofía "There's More Than One Way To Do It" (TMTOWTDI, pronunciado Tim Toady) (Hay más de una forma de hacer algo). Por lo contrario, el lema de Python es: Debería de haber solo una, y preferiblemente una, manera de hacerlo.

Perl tiene sus horrores pero también tiene características grandiosas. En este respecto, es como cualquier otro lenguage de programación que jamás hay sido creado.

La intención de este documento es ser informativo, no evangélico. Su objectivo son personas, que como yo:

Este documento intenta ser tan breve como sea posible pero no más breve.

Notas preliminares

Hola Mundo

Un script de Perl es un archivo de texto con la extensión .pl.

Aquí esta el código completo de holamundo.pl:

use strict;
use warnings;

print "Hola mundo";
Los scripts de Perl son interpretados por el interpretador de Perl, perl o perl.exe:

perl holamundo.pl [arg0 [arg1 [arg2 ...]]]

Otras notas breves. La sintaxis de Perl es altamente permisiva y te permite hacer cosas que resultan en sentencias que lucen ambiguas con comportamiento impredecible. No tiene sentido en tratar de explicar cuales son estos comportamientos dado que quieres evitarlos. La manera de evitarlos es poner use strict; use warnings; al inicio de cada uno de tus scripts de Perl o módulos. Instrucciones de la forma use foo; son llamadas pragmas. Una pragma es una señal al interpretador perl (or perl.exe) que toma effecto cuando la validación sintáctica inicial está siendo realizada, antes que el mismo programa comience a ser ejecutado. Estas líneas no tienen ningún effecto cuando el interpretador las encuentra al tiempo de ejecución.

El punto y coma, ;, termina una sentencia. El símbolo # comienza un comentario. Un comentario permanece hasta al final de la línea. Perl no tiene sintaxis para creer un bloque de comentarios.

Variables

Las variables de Perl vienen en tres tipos: escalares, arrays y hashes. Cada tipo tiene su propio sigilo: $, @ y % respectivamente. Las variables son declaradas usando my y permanecen al alcance hasta el final del bloque o archivo.

Variable escalares

Una variable escalar puede contener:

my $indef = undef;
print $indef; # imprime la cadena de texto vacía "" y levanta una advertencia

# undef implícito:
my $indef2;
print $indef2; # imprime "" y levanta la misma advertencia
my $num = 4040.5;
print $num; # "4040.5"
my $cadena = "mundo";
print $cadena; # "mundo"

(Trataré el tema de las referencias en un momento.)

La concatenación de cadena de texto se obtiene con el uso del operador . (igual que PHP):

print "Hola ".$cadena; # "Hola mundo"

"Booleanos"

Perl no tiene tipo de dato booleano. Un escalar en una sentencia if evalúa a un booleano "falso" si y solo si es uno de los siguientes:

La documentación de Perl repetidamente proclama que las funciones devuelven valores "verdaderos" o "falsos" en algunas situationes. En práctica, cuando se dice que una función devuelve "verdadero" usualmente devuelve 1 y cuando se dice que devuelve "falso" usualmente devuelve la cadena de texto vacía, "".

Tipado débil

Es imposible determinar si un escalar contiene un "número" o una "cadena de texto". Más precisamente, no debería ser necesario hacer esto. Si un escalar se comporta como un número o una cadena de texto depende en el operador con el cual es usado. Cuando se usa como un cadena de texto, un escalar se comportará como una cadena de texto. Cuando se usa como un número, un escalar se comportará como un número (levantando una advertencia si no es posible):

my $str1 = "4G";
my $str2 = "4H";

print $str1 .  $str2; # "4G4H"
print $str1 +  $str2; # "8" con dos advertencias
print $str1 eq $str2; # "" (cadena de texto vacía, i.e. falso)
print $str1 == $str2; # "1" con dos advertencias

# El error clásico
print "yes" == "no"; # "1" con dos advertencias; ambos valores evalúan a 0 cuando se usan como números

La lección es que se debe usar el operador correcto en la situación correcta. Existen operadores separados para comparar escalares como números y comparar escalares como cadenas de texto:

# Operadores numéricos:  <,  >, <=, >=, ==, !=, <=>, +, *
# Operadores de cadenas de texto:    lt, gt, le, ge, eq, ne, cmp, ., x

Variables arrays

Una variable array es una lista de escalares indizados por números enteros comenzando con el 0. En Python esto se le conoce como una lista, y en PHP es conocido como un array. Un array se declara con una lista de escalares rodeados por paréntesis:

my @array = (
    "imprime",
    "estas",
    "palabras",
    "para",
    "mi", # una coma colgante es okay
);

Se debe usar el símbolo de dólar para accesar un valor particular de un array, dado que el valor que se desea accesar no es un array pero un escalar.

print $array[0]; # "imprime"
print $array[1]; # "estas"
print $array[2]; # "palabras"
print $array[3]; # "para"
print $array[4]; # "mi"
print $array[5]; # returna undef, imprime "" y levanta una advertencia

Se pueden usar índices negativos para accesar entradas del array comenzando por el final y trabajando hacia atrás:

print $array[-1]; # "mi"
print $array[-2]; # "para"
print $array[-3]; # "palabras"
print $array[-4]; # "estas"
print $array[-5]; # "imprime"
print $array[-6]; # returna undef, imprime "" y levanta una advertencia

No hay colisión entre un escalar $var y un array @var que contiene una entrada escalar $var[0]. Sin embargo, la persona leyendo el código puede confundirse y por lo tanto, es recomendado evitar nombrar variables de diferentes tipos con el mismo nombre.

Para obtener la longitud (tamaño) de un array:

print "Este array contiene ".(scalar @array)." elementos"; # "Este array contiene 5 elementos"
print "El último índice ocupado es ".$#array; # "El último índice ocupado es "

Los argumentos con el cual el script original de Perl fue invocado se almacenan en la variable array incorporada @ARGV.

Las variables pueden ser interpoladas dentro de las cadenas de texto:

print "Hola $cadena"; # "Hola mundo"
print "@array";        # "imprime estas palabras para mi"

Precaución. Un día de estos tu pondrás la dirección de email de alguien dentro de una cadena de texto, "jeff@gmail.com". Esto causará que Perl busque una variable array llamada @gmail para interpolar dentro de la cadena de texto y no la encontrará, lo que resultará en un error durante el tiempo de ejecución. La interpolación se puede prevenir en dos maneras: usando la barra inversa (\) para escapar el sigilo, o usando las comillas simples ('') en vez de las comillas inglesas ("").

print "Hola \$cadena";  # "Hola $cadena"
print 'Hola $cadena';   # "Hola $cadena"
print "\@array";        # "@array"
print '@array';         # "@array"

Variables hashes

Una variable hash es una lista de escalares indizados por cadenas de texto. En Python se le conoce como un diccionario y en PHP como un array.

my %cientificos = (
    "Newton"   => "Isaac",
    "Einstein" => "Albert",
    "Darwin"   => "Charles",
);

Nota lo similar que es esta declaración a la declaración de un array. De hecho, el símbolo de flecha doble => es conocido como la "coma gorda" porque es solamente un sinónimo de la coma usada para separar elementos. Un hash se declara usando una lista con un número par de elementos, donde todos los elementos con numeración par (0, 2, ...) son tomados como cadenas de texto.

Una vez más, tu debes usar el signo de dólar para accesar un valor particular de un hash, dado el valor que se desea accesar no es un hash pero un escalar:

print $cientificos{"Newton"};   # "Isaac"
print $cientificos{"Einstein"}; # "Albert"
print $cientificos{"Darwin"};   # "Charles"
print $cientificos{"Dyson"};    # devuelve undef, imprime "" y levanta una advertencia

Nota los paréntesis de llaves "{}" usados aquí. Otra vez, no hay colisión entre un escalar $var y un hash %var que contiene una entrada escalar $var{"foo"}.

Se puede convertir un hash directamente a un array con el doble de entradas, alternando entre claves y valores (y lo reverso es igualmente fácil):

my @cientificos = %cientificos;

Sin embargo, a diferencia de un array, las llaves de un hash no poseen ningún orden intrínsico. Las llaves serán returnadas en el orden que sea más eficiente. Aunque el orden ha sido reorganizado en el siguiente array, los pares de llaves y valores han sido preservados:

print "@cientificos"; # algo como "Einstein Albert Darwin Charles Newton Isaac"

Para resumir, se deben usar corchetes para accesar un valor de un array y se deben utilizar paréntesis de llaves para accesar un valor de un hash. Los corchetes son efectivamente operadores numéricos y los paréntesis de llaves son efectivamente operadores de cadenas de texto. El hecho de que el índice proporcionado sea un número o una cadena de texto no tiene significancia alguna:

my $datos = "naranja";
my @data = ("purpura");
my %data = ( "0" => "azul");

print $datos;      # "naranja"
print $datos[0];   # "purpura"
print $datos["0"]; # "purpura"
print $datos{0};   # "azul"
print $datos{"0"}; # "azul"

Listas

De nuevo, una lista en Perl es algo diferente a un array o un hash. Ya has observado varias listas:

(
    "imprime",
    "estas",
    "palabras",
    "para",
    "mi",
)

(
    "Newton"   => "Isaac",
    "Einstein" => "Albert",
    "Darwin"   => "Charles",
)

Una lista no es una variable. Una lista es un valor efímero que puede ser asignado a una variable array o una variable hash. Esto es porque la sintaxis para declarar arrays y hashes es idéntica. Hay muchas situaciones donde los términos "lista" y "array" son usados de manera intercambiable pero igualmente hay muchas otras situaciones donde listas y arrays muestran diferencias sutiles y comportamientos extremadamentes confusos.

Okay. Recuerda que la coma gorda (=>) es solamente un coma (,) disfrazada. Mira el siguiente ejemplo:

("uno", 1, "tres", 3, "cinco", 5)
("uno" => 1, "tres" => 3, "cinco" => 5)

El uso de => da una pista que una de estas listas es la declaración de un array y la otra es la declaración de un hash. Sin embargo, por si solas, ningunas de ellas son declaraciones de nada. Son solamente listas. Listas idénticas. También:

()

Aquí no hay ni siquiera una pista. Esta lista podría ser usada para declarar un array vacío o un hash vacío y el interpretador perl claramente no tiene ninguna manera de acertar. Una vez que tu entiendas este aspecto extraño de Perl, entederás porque lo siguiente es verdadero: Los valores de una listas no pueden ser contenidos unos dentro de otros. Inténtalo:

my @array = (
    "manzanas",
    "bananas",
    (
        "interior",
        "lista",
        "varias",
        "entradas",
    ),
    "cerezas",
);

Perl no tiene maneras de saber si ("interior", "lista", "varias", "entradas") está supuesto ser un array interior o un hash interior. Debido a esto, Perl asume que no es ninguno de ellos y aplana la lista, resultando en una sola lista larga:

print $array[0]; # "manzanas"
print $array[1]; # "bananas"
print $array[2]; # "interior"
print $array[3]; # "lista"
print $array[4]; # "varias"
print $array[5]; # "entradas"
print $array[6]; # "cerezas"

Lo mismo es cierto si se usa la coma gorda:

my %hash = (
    "cerveza" => "buena",
    "bananas" => (
        "verde"  => "espera",
        "amarillo" => "come",
    ),
);

# Lo anterior levanta un advertencia debido a que el array fue declarado usando una lista con 7 elementos.
# Recuerda que un hash debe tener un número par de elementos.

print $hash{"cerveza"};    # "good"
print $hash{"bananas"};    # "verde"
print $hash{"espera"};     # "amarillo";
print $hash{"come"};       # undef, así que imprime "" y levanta una advertencia

Por supuesto, esto facilita la concatenación de varios arrays:

my @huesos   = ("húmero", ("mandíbula", "cráneo"), "tibia");
my @dedos = ("pulgar", "índice", "medio", "corazón", "meñique");
my @partes   = (@huesos, @dedos, ("pies", "dedo"), "globo ocular", "nudillo");
print @partes;

Más acerca de esto en un momento.

Contexto

Una de las características más distintivas de Perl es que su código es sensitivo contextualmente. Cada expresión en Perl es evaluada en un contexto escalar o un contexto de lista, dependiendo si se espera producir un escalar o una lista. Si no se sabe el contexto en el cual una expresión es evaluada, es imposible determinar su resultado.

Una asignación escalar como my $escalar = evalúa su expresión en un contexto escalar. Aquí la expresión es "Mendeleev":

my $escalar = "Mendeleev"; 

Una asignación de array o hash como my @array = o my %hash = evalúa su expresión en contexto de lista. Aquí, la expresión es ("Alfa", "Beta", "Gama", "Pi") (o ("Alfa" => "Beta", "Gama" => "Pi"), las dos son equivalentes):

my @array = ("Alfa", "Beta", "Gama", "Pi");
my %hash = ("Alfa" => "Beta", "Gama" => "Pi");

Una expresión escalar evaluada en contexto de lista es convertida silenciosamente en una lista con un solo valor:

my @array = "Mendeleev"; # lo mismo que 'my @array = ("Mendeleev");'

Una expresión (de tipo) array evaluada en contexto escalar devuelve la longitud del array:

my @array = ("Alfa", "Beta", "Gama", "Pi");
my $escalar = @array;
print $escalar; # "4"

Una expresión (de tipo) lista (una lista es diferente a un array, recuerdas?) evaluada en un contexto escalar no devuelve la longitud de la lista pero el último elemento escalar en la lista:

my $scalar = ("Alfa", "Beta", "Gama", "Pi");
print $escalar; # "Pi"

La función incorporada print evalúa todos sus argumentos en un contexto de lista. De hecho, print acepta una lista ilimitada de argumentos e imprime cada uno de ellos detrás del otro, lo que significa que puede ser utilizada para imprimir arrays directamente:

my @array = ("Alfa", "Beta", "Goo");
my $escalar = "-X-";
print @array;              # "AlfaBetaGoo";
print $scalar, @array, 98; # "-X-AlfaBetaGoo98";

Precaución. Muchas expresiones y funciones incorporadas en Perl muestran diferente comportamiento dependiendo en el contexto en el cual son evaluadas. El ejemplo más prominente es la función reverse. En un contexto de lista, reverse trata sus argumentos como una lista e invierte la lista. En un contexto escalar, reverse concatena todos los elementos de la lista juntos y después invierte dicha concatenación como si fuera una sola palabra.

print reverse "hola mundo"; # "hola mundo"

my $cadena = reverse "hola mundo";
print $cadena; # "odnum aloh"

Se puede forzar cualquier expresión a ser evaluada en un contexto escalar usando la función incorporada scalar:

print scalar reverse "hola mundo"; # "odnum aloh"

Recuerdas como usamos scalar anteriormente para obtener la longitud de un array?

Referencias y estructuras de datos anidadas

De la misma manera que listas no pueden contener otras listas como elementos, arrays y hashes no pueden contener otros arrays y hashes como elementos. Ellos pueden contener solamente escalares. Observa lo que sucede cuando lo intentamos:

my @exterior = ("Sol", "Mercurio", "Venus", undef, "Marte");
my @interior = ("Tierra", "Luna");

$exterior[3] = @interior;

print $exterior[3]; # "2"

$exterior[3] es un escalar y por lo tanto, demanda un valor escalar. Cuando intentas asignarle un valor array como @interior, @interior es evaluado en contexto escalar. Esto es lo mismo que asignar scalar @interior, el cual es la longitud del array @interior, que es 2.

Sin embargo, una variable escalar puede contener una referencia a cualquier variable, incluyendo una variable array o una variable hash. Esta es la manera en la cual complicadas estructuras de datos son creadas en Perl.

Una referencia es creada usando una barra inversa.

my $color    = "Indigo";
my $escalarRef = \$color;

Cada vez que uses el nombre de la variable, podrías poner algunos paréntesis de llaves y dentro de ellos, poner una referencia a la variable.

print $color;            # "Indigo"
print $escalarRef;       # e.g. "SCALAR(0x182c180)"
print ${ $escalarRef };  # "Indigo"

Siempre y cuando el resultado no sea ambiguo, los paréntesis de llaves pueden ser omitidos también:

print $$escalarRef; # "Indigo"

Si la referencia es una referencia a una variable array o una variable hash, se puede usar el popular operador flecha -> para accesar los datos:

my @colores = ("Rojo", "Naranja", "Amarillo", "Verde", "Azul");
my $arrayRef = \@colores;

print $colores[0];       # acceso directo a un elemento del array
print ${ $arrayRef }[0]; # uso de referencia para accesar el array
print $arrayRef->[0];    # exactamente lo mismo a lo anterior

my %pesoAtomicos = ("Hidrógeno" => 1.008, "Helio" => 4.003, "Magnesio" => 54.94);
my $hashRef = \%pesoAtomicos;

print $pesonAtomicos{"Helio"}; # acceso directo a un elemento del hash
print ${ $hashRef }{"Helio"};  # uso de referencia para accesar el hash
print $hashRef->{"Helio"};     # exactamente lo mismo - muy común

Declarando una estructura de datos

Los siguientes son cuatro ejemplos pero in práctica el último es el más usado.

my %dueno1 = (
    "nombre" => "Santa Claus",
    "fecha_nacimiento"  => "1882-12-25",
);

my $dueno1Ref = \%dueno1;

my %dueno2 = (
    "nombre" => "Mickey Mouse",
    "fecha_nacimiento"  => "1928-11-18",
);

my $dueno2Ref = \%dueno2;

my @duenos = ( $dueno1Ref, $dueno2Ref );

my $duenosRef = \@duenos;

my %cuenta = (
    "numero" => "12345678",
    "abierta" => "2000-01-01",
    "duenos" => $duenosRef,
);

Esto es innecesario y obviamente laborioso porque puede reducirse a lo siguiente:

my %dueno1 = (
    "numero" => "Santa Claus",
    "fecha_nacimiento"  => "1882-12-25",
);

my %dueno2 = (
    "nombre" => "Mickey Mouse",
    "fecha_nacimiento"  => "1928-11-18",
);

my @duenos = ( \%dueno1, \%dueno2 );

my %cuenta = (
    "numero" => "12345678",
    "abierta" => "2000-01-01",
    "duenos" => \@owners,
);

Es también posible declarar arrays y hashes anónimos usando diferentes símbolos. Usa corchetes para declarar un array anónimo y paréntesis de llaves para declarar un hash anónimo. El valor devuelto en cada caso es una referencia a la estructura de dato anónima en cuestión. Observa cuidadosamente, esto resulta exactamente en el mismo %cuenta anterior:

# Los paréntesis de llaves denotan un hash anónimo
my $dueno1Ref = {
    "nombre" => "Santa Claus",
    "fecha_nacimiento"  => "1882-12-25",
};

my $dueno2Ref = {
    "nombre" => "Mickey Mouse",
    "fecha_nacimiento"  => "1928-11-18",
};

# Los corchetes denotan un array anónimo
my $duenosRef = [ $dueno1Ref, $dueno2Ref ];

my %cuenta = (
    "numero" => "12345678",
    "abierta" => "2000-01-01",
    "duenos" => $ownersRef,
);

O, más compacto (y esta la forma que deberías usar al declarar estructuras de datos complejas en línea):

my %cuenta = (
    "numero" => "31415926",
    "abierta" => "3000-01-01",
    "duenos" => [
        {
            "nombre" => "Philip Fry",
            "fecha_nacimiento"  => "1974-08-06",
        },
        {
            "nombre" => "Hubert Farnsworth",
            "fecha_nacimiento"  => "2841-04-09",
        },
    ],
);

Extrayendo información de una estructura de datos

Ahora, asumamos que todavías tienes %cuenta alrededor pero lo demás (si hubiera algo más) está fuera de alcance. Puedes imprimir la información invirtiendo el mismo procedimiento en cada caso. Otra vez, cuatros ejemplos de los cuales el último es el más útil:

my $duenosRef = $cuenta{"duenos"};
my @duenos    = @{ $duenosRef };
my $dueno1Ref = $duenos[0];
my %dueno1    = %{ $dueno1Ref };
my $dueno2Ref = $duenos[1];
my %dueno2    = %{ $dueno2Ref };
print "Número de cuenta ", $cuenta{"numero"}, "\n";
print "Abierta ", $cuenta{"abierta"}, "\n";
print "Dueños:\n";
print "\t", $dueno1{"nombre"}, " (nació ", $dueno1{"fecha_nacimiento"}, ")\n";
print "\t", $dueno2{"nombre"}, " (nació ", $dueno2{"fecha_nacimiento"}, ")\n";

O con más brevedad:

my @duenos = @{ $cuenta{"duenos"} };
my %dueno1 = %{ $duenos[0] };
my %dueno2 = %{ $duenos[1] };
print "Número de cuenta ", $cuenta{"numero"}, "\n";
print "Abierta ", $cuenta{"abierta"}, "\n";
print "Dueños:\n";
print "\t", $dueno1{"nombre"}, " (nació ", $dueno1{"fecha_nacimiento"}, ")\n";
print "\t", $dueno2{"nombre"}, " (nació ", $dueno2{"fecha_nacimiento"}, ")\n";

O usando referencias y el operador ->:

my $duenosRef = $cuenta{"duenos"};
my $dueno1Ref = $duenosRef->[0];
my $dueno2Ref = $duenosRef->[1];
print "Número de cuenta ", $cuenta{"numero"}, "\n";
print "Abierta ", $cuenta{"abierta"}, "\n";
print "Dueños:\n";
print "\t", $dueno1Ref->{"nombre"}, " (nació ", $dueno1Ref->{"fecha_nacimiento"}, ")\n";
print "\t", $dueno2Ref->{"nombre"}, " (nació ", $dueno2Ref->{"fecha_nacimiento"}, ")\n";

Y si saltamos todos los valores intermedios:

print "Número ", $cuenta{"numero"}, "\n";
print "Abierta ", $cuenta{"abierta"}, "\n";
print "Dueños:\n";
print "\t", $cuenta{"duenos"}->[0]->{"nombre"}, " (nació ", $cuenta{"duenos"}->[0]->{"fecha_nacimiento"}, ")\n";
print "\t", $cuenta{"duenos"}->[1]->{"nombre"}, " (nació ", $cuenta{"duenos"}->[1]->{"fecha_nacimiento"}, ")\n";

Cómo dispararte en el pie con referencias a arrays

Este array tiene cinco elementos:

my @array1 = (1, 2, 3, 4, 5);
print @array1; # "12345"

Este array, sin embargo, tiene UN elemento el cual es una referencia a un array anónimo con cinco elementos:

my @array2 = [1, 2, 3, 4, 5];
print @array2; # e.g. "ARRAY(0x182c180)"

Este escalar es una referencia a un array anónimo con cinco elementos:

my $array3Ref = [1, 2, 3, 4, 5];
print $array3Ref;      # e.g. "ARRAY(0x22710c0)"
print @{ $array3Ref }; # "12345"
print @$array3Ref;     # "12345"

Condiciones

if ... elsif ... else ...

No sorpresas aquí, otra que la ortografía de elsif:

my $palabra = "antidisestablishmentarianism";
my $strlen = length $palabra;

if($strlen >= 15) {
    print "'", $palabra, "' es una palabra larguísima";
} elsif(10 <= $strlen && $strlen < 15) {
    print "'", $word, "' es una palabra de longitud mediana";
} else {
    print "'", $word, "' es una palabra corta";
}

Perl provee una sintaxis más corta de la sentencia de la condición if y es altamente recomendada para sentencias cortas:

print "'", $palabra, "' es actualmente enorme" if $strlen >= 20;

unless ... else ...

my $temperatura = 20;

unless($temperatura > 30) {
    print $temperatura, " grados Celsios no es muy caliente";
} else {
    print $temperatura, " grados Celsios es actualmente bien caliente";
}

Los bloques unless es mejor evitarlos como la plaga ya que son muy confusos. Un bloque "unless [... else]" puede ser refactorizado en un bloque "if [... else]" negando la condición [o manteniendo la condición e intercambiando los bloques]. Afortunadamente no hay elsunless.

Esto, en comparación, es altamente recomendado ya que es fácil de leer:

print "Oh no, está bien frío" unless $temperature > 15;

Operador ternario

El operador ternario ?: permite que sentencias if puedan ser incluidas en una sentencia. El uso canónico para el operador ternario son formas singulares y plurales:

my $ganancia = 48;
print "Ganaste ", $ganancia, " ", ($ganancia == 1 ? "punto de experiencia" : "puntos de experiencias"), "!";

Nota: Casos singulares y plurales son mejores si son deletreados en su totalidad en ambos casos. No hagas algo ingenioso como en el siguiente ejemplo, dado que cualquier persona escaneando la base de datos para reemplazar las palabras "pez" o "peces" nunca las encontrará en la siguiente línea:

my $numero = 1;
print "Hay ", $numero, " pe", ($numero == 1 ? "z" : "ces"), "!";

Los operadores ternarios pueden ser anidados:

my $huevos = 5;
print "Tienes ", $huevos == 0 ? "no huevos" :
                 $huevos == 1 ? "un huevo"  :
                 "algunos huevos";

La sentencia if evalúa las condiciones en un contexto escalar. Por ejemplo, if(@array) devuelve verdadero si y solo si @array tiene por lo menos un elemento (1 o más). No importa lo que sean esos elementos - ellos pueden contener undef u otro valor falso.

Loops

Hay más de una forma de hacer algo.

Perl posee un loop convencional while:

my $i = 0;
while($i < scalar @array) {
    print $i, ": ", $array[$i];
    $i++;
}

Perl también ofrece la palabra until:

my $i = 0;
until($i >= scalar @array) {
    print $i, ": ", $array[$i];
    $i++;
}

Los loops do son casi siempre equivalentes a los loops anteriores (aunque una advertencia sería levantada si array estuviera vacío):

my $i = 0;
do {
    print $i, ": ", $array[$i];
    $i++;
} while ($i < scalar @array);

y

my $i = 0;
do {
    print $i, ": ", $array[$i];
    $i++;
} until ($i >= scalar @array);

Loops for en el estilo básico de C también están disponibles. Nota como ponemos un my dentro de la declaración for, lo que declara la variable $i dentro del alcance del loop solamente:

for(my $i = 0; $i < scalar @array; $i++) {
    print $i, ": ", $array[$i];
}
# $i ha terminado de existir aquí, lo cual es más ordenado.

Este tipo de loop for es considerado anticuado y es mejor evitarlo donde sea posible. Iteraciones nativas sobre una lista son mucho mejor. Nota: A diferencia de PHP, las palabras for y foreach son sinónimas. Solamente usa cualquiera que te sea más legible:

foreach my $cadena ( @array ) {
    print $cadena;
}

Si necesitas los índices, el operador range .. crea una lista anónima de números enteros:

foreach my $i ( 0 .. $#array ) {
    print $i, ": ", $array[$i];
}

No se puede iterar sobre un hash. Sin embargo, se puede iterar sobre sus llaves. Usa la function incluida keys para obtener un array que contiene todas las llaves del hash. Y después usa la siguiente táctica con el loop foreach que usamos anteriormente:

foreach my $llave (keys %cientificos) {
    print $llave, ": ", $cientificos{$llave};
}

Dado que un hash no tiene un orden intrínsico, las llaves pueden ser devueltas en cualquier orden. Usa la función incluida sort para ordenar el array de llaves alfabéticamente con antelación:

foreach my $llave (sort keys %cientificos) {
    print $llave, ": ", $cientificos{$llave};
}

Si no se provee un iterador explícito, Perl usa el iterador por defecto, $_. $_ es la primera y más amistosas de las variables incluidas:

foreach ( @array ) {
    print $_;
}

Si usas el iterador por defecto y deseas utilizar una sola sentencia dentro de tu loop, puedes utilizar la sintaxis super-corta de un loop:

print $_ foreach @array;

Control de loop

next y last pueden ser utilizados para controlar el progreso de un loop. En la mayoría de lenguajes de programación, se les conoce como continue y break respectivamente. Opcionalmente, podemos proveer una etiqueta para cada loop. Por convención, las etiquetas son escritas en letras MAYÚSCULAS. Habiendo etiquetado el loop, next y last pueden referirse a esa etiqueta. Este ejemplo encuentra los números primos menores que 100:

CANDIDATO: for my $candidato ( 2 .. 100 ) {
    for my $divisor ( 2 .. sqrt $candidato ) {
        next CANDIDATO if $candidato % $divisor == 0;
    }
    print $candidato." es primo\n";
}

Funciones de un array

Modificación de un array

Usaremos @pila para demostrar lo siguiente:

my @pila = ("Fred", "Eileen", "Denise", "Charlie");
print @pila; # "FredEileenDeniseCharlie"

pop extrae y devuelve el último elemento de un array. Se puede visualizar como la parte superior de la pila (stack en inglés):

print pop @pila; # "Charlie"
print @pila;     # "FredEileenDenise"

push añade elementos extras al final del array:

push @pila, "Bob", "Alice";
print @pila; # "FredEileenDeniseBobAlice"

shift extrae y returna el primer elemento del array:

print shift @pila; # "Fred"
print @pila;       # "EileenDeniseBobAlice"

unshift inserta nuevos elementos al inicio del array:

unshift @pila, "Hank", "Grace";
print @pila; # "HankGraceEileenDeniseBobAlice"

Es importante recalcar que estas funciones modifican el array original. Por lo tanto se debe ser cuidadoso si la preservación del array en su estado original es importante.

pop, push, shift y unshift son todas casos especiales de splice. splice remueve y devuelve una porción de un array, reemplazándola con una porción distinta.

print splice(@pila, 1, 4, "<<<", ">>>"); # "GraceEileenDeniseBob"
print @pila;                             # "Hank<<<>>>Alice"

Creando un array nuevo a partir de uno viejo

Perl provee las siguientes funciones que actúan en arrays para crear otros arrays.

La función join concatena varias cadenas de texto en una:

my @elementos = ("Antimonio", "Arsénico", "Aluminio", "Selenio");
print @elementos;             # "AntimonioArsénicoAluminioSelenio"
print "@elementos";           # "Antimonio Arsénico Aluminio Selenio"
print join(", ", @elements); # "Antimonio, Arsénico, Aluminio, Selenio"

En un contexto de lista, la función reverse devuelve una lista en orden inverso. En un contexto escalar, reverse concatena la lista completa y después la invierte como una sola palabra.

print reverse("Hola", "Mundo");          # "MundoHola"
print reverse("HelloWorld");             # "HolaMundo"
print scalar reverse("HelloWorld");      # "odnuMaloH"
print scalar reverse("Hola", "Mundo");   # "odnuMaloH"

La función map toma un array como entrada y aplica una operación a cada escalar $_ en dicho array. Después construye un nuevo array usando los resultados. La operación aplicada es proveída en la forma de una expresión singular dentro de paréntesis de llaves:

my @capitales = ("Baton Rouge", "Indianapolis", "Columbus", "Montgomery", "Helena", "Denver", "Boise");

print join ", ", map { uc $_ } @capitales;
# "BATON ROUGE, INDIANAPOLIS, COLUMBUS, MONTGOMERY, HELENA, DENVER, BOISE"

La función grep toma un array como entrada y returna un array filtrado. La sintaxis es similar a la sintaxis de map. En este caso, el segundo argumento es evaluado por cada escalar $_ del array de entrada. Si un valor booleano verdadero es devuelto, el escalar es colocado en el array de salida. Si es falso, no es colocado.

print join ", ", grep { length $_ == 6 } @capitales;
# "Helena, Denver"

Obviamente, la longitud del array de salida es equivalente al número de coincidencias, lo que significa que grep puede utilizarse para verificar si un array contiene un elemento en particular:

print scalar grep { $_ eq "Columbus" } @capitales; # "1"

grep y map pueden ser combinadas para formar una lista autodefinida (list comprehension en inglés), que es una característica excepcionalmente poderosa usualmente ausente en muchos lenguajes de programación.

Por defecto, la función sort devuelve el array de entrada, ordenado lexicalmente (alfábeticamente):

my @elevaciones = (19, 1, 2, 100, 3, 98, 100, 1056);

print join ", ", sort @elevaciones;
# "1, 100, 100, 1056, 19, 2, 3, 98"

Sin embargo, similarmente a grep y map, puedes proveer a sort con tu propio código. Ordenamiento siempre se hace usando una serie de comparaciones entre dos elementos. Tu bloque recibe $a y $b como entradas y devuelve -1 si $a "es menor que" $b, 0 si ambas son "iguales" o 1 si $a es "mayor que" $b.

El operador cmp hace comparaciones entre cadenas de texto:

print join ", ", sort { $a cmp $b } @elevaciones;
# "1, 100, 100, 1056, 19, 2, 3, 98"

El operador "spaceship", <=>, hace comparaciones entre números:

print join ", ", sort { $a <=> $b } @elevaciones;
# "1, 2, 3, 19, 98, 100, 100, 1056"

$a y $b son siempre escalares pero pueden ser referencias a objetos complejos que son difíciles de comparar. Si necesitas más espacio para la comparación, puedes crear una subrutina separada y proveer su nombre:

sub comparador {
    # mucho código aquí...
    # devuelve -1, 0 o 1
}

print join ", ", sort comparador @elevaciones;

No se puede hacer esto con las operaciones grep o map.

Nota como nunca se provee explícitamente la subrutina y el bloque con $a y $b. Igual que $_, $a y $b son, en efecto, variables globales que son pobladas con un par de valores que se comparan cada vez.

Funciones incorporadas

Para este entonces, ya has visto por lo menos una docena de funciones incorporadas: print, sort, map, grep, keys, scalar y más. Las funciones incorporadas son unas de las grandes fortalezas de Perl. Ellas

El mejor consejo concerniente a funciones incorporadas es saber que existen. Échale un ojo a la documentación para futuras referencias. Si estás haciendo una tarea que sientes que es de bajo nivel y lo suficientemente común, hay una posibilidad de que haya sido resuelta ya.

Subrutinas definidas por el usuario

Las subrutinas son declaradas usando la palabra sub. A diferencia de las funciones incorporadas, las subrutinas definidas por el usuario siempre aceptan la misma entrada: una lista de escalares. Esta lista puede, por supuesto, tener un solo elemento or estar vacía. Un único escalar es tomado como una lista con un solo elemento. Un hash con N elementos es tomado como una lista con 2N elementos.

Aunque los corchetes son opcionales, las subrutinas deberían ser siempre invocadas usando corchetes, incluso cuando son invocadas sin argumentos. Esto deja en claro que la invocación de una subrutina está ocurriendo.

Una vez dentro de una subrutina, los argumentos están disponibles usando la variable incorporada @_. Ejemplo:

sub agrega_guion {

  # Extrae el primer argumento del array e ignora el resto
  my $palabra = shift @_;

  # Una ingeniosa lista autodefinida
  $palabra = join "-", map { substr $palabra, $_, 1 } (0 .. (length $palabra) - 1);
  return $palabra;
}

print agrega_guion("exterminar"); # "e-x-t-e-r-m-i-n-a-r"

Llamadas por referencia en Perl

A diferencia de otros lenguajes de programación, Perl hace llamadas por referencia. Esto significa que las variables (o los valores) disponibles dentro del cuerpo de una subrutina no son copias de las originales. Ellas son las originales.

my $x = 7;

sub reasignar {
  $_[0] = 42;
}

reasignar($x);
print $x; # "42"

Si intentas algo como

reasignar(8);

un error ocurre y la ejecución termina, porque la primera línea de reasignar() es equivalente a

8 = 42;

lo cual obviamente no tiene sentido (No se puede asignar un número a un número).

La lección aquí es que en el cuerpo de una subrutina, tu deberías siempre desempacar tus argumentos antes de usarlos.

Desempacando argumentos

Hay más de una manera de desempacar @_, pero unas son superiores a otras.

La subrutina indentar_izq en el siguiente ejemplo agrega un nivel particular de indentación a una cadena de texto usando el carácter provisto. (El operador x concatena varias copias de una cadena de texto en una fila.)(Nota: Por brevedad, estas subrutinas no tienen la necesaria detección de errores, i.e. asegurar que el carácter indentador es solamente un carácter, chequear que el ancho sea mayor or igual que la longitud de la actual cadena de texto, chequear que todos los argumentos hayan sido pasados, etc.)

print indentar_izq("hola", 10, "+"); # "+++++hola"
  1. Desempacar @_ entrada por entrada es efectivo pero no tan hermoso:

    sub indentar_izq {
        my $cadenaVieja = $_[0];
        my $ancho       = $_[1];
        my $charInd     = $_[2];
        my $cadenaNueva = ($charInd x ($ancho - length $cadenaVieja)) . $cadenaVieja;
    
        return $cadenaNueva;
    }
    
  2. Desempacar @_ removiendo datos usando shift es recomendado para un máximo de 4 argumentos:

    sub indentar_izq {
        my $cadenaVieja = shift @_;
        my $ancho       = shift @_;
        my $charInd     = shift @_;
        my $cadenaNueva = ($charInd x ($ancho - length $cadenaVieja)) . $cadenaVieja;
    
        return $cadenaNueva;
    }
    

    Si ningún array es provisto, la función shift opera en @_ implícitamente. Este manera de hacerlo es bien común:

    sub indentar_izq {
        my $cadenaVieja  = shift;
        my $ancho        = shift;
        my $charInd      = shift;
        my $cadenaNueva  = ($charInd x ($ancho - length $cadenaVieja)) . $cadenaVieja;
    
        return $cadenaNueva;
    }
    

    Con más de 4 argumentos, es difícil mantener un registro de que lo está siendo asignado y donde está siendo asignado.

  3. Se puede desempacar @_ de una sola vez usando varias asignaciones escalares simultáneas. De nuevo, esto es recomendado para un máximo de 4 argumentos:

    sub indentar_izq {
        my ($cadenaVieja, $ancho, $charInd) = @_;
        my $cadenaNueva = ($charInd x ($ancho - length $cadenaVieja)) . $cadenaVieja;
        return $cadenaNueva;
    }
    
  4. Para subrutinas con un gran número de argumentos o donde los argumentos son opcionales o no pueden ser usados en combinaciones con otros, la mejor práctica es requirir que el usuario provea un hash de argumentos cuando la subrutina que se invoca, y después desempacar @_ en un hash de argumentos. En este caso, nuetra subrutina luciría un poco diferente:

    print indentar_izq("cadenaVieja" => "pod", "ancho" => 10, "charInd" => "+");
    

    Y la subrutina luce de esta manera:

    sub indentar_izq {
        my %args = @_;
        my $cadenaNueva = ($args{"charInd"} x ($args{"ancho"} - length $args{"cadenaVieja"})) . $args{"cadenaVieja"};
    
        return $cadenaNueva;
    }
    

Devolviendo valores

Como otras expresiones en Perl, las invocaciones de una subrutina pueden mostrar comportamiento contextual. Se puede usar la función wantarray (que debería ser llamada wantlist pero no importa) para detectar el contexto en el cual la subrutina está siendo evaluada y devolver un resultado apropiado para el contexto:

sub subrutinaContextual {
    # El invocador quiere una lista. Devuelve una lista
    return ("Everest", "K2", "Etna") if wantarray;

    # El invocador quiere un escalar. Devuelve un escalar
    return 3;
}

my @array = subrutinaContextual();   # contexto de lista
print @array; # "EverestK2Etna"

my $escalar = subrutinaContextual(); # contexto escalar
print $scalar; # "3"

Llamadas al systema

Mis excusas si ya sabes los siguientes conceptos que no están directamente relacionado con Perl. Cada vez que un proceso finaliza en un sistema Windows o Linux (y asumo lo mismo para otros sistemas), dicho proceso termina con una palabra de estado de 16-bit. Los 8-bits más altos constituyen un código de error entre 0 y 255 inclusivo, con el 0 representando convencionalmente un éxito no calificado, y otros valores representando varios grados de fallo. Los otros 8-bits son menos examinados - "ellos reflejan el modo de fallo, como una señal de muerte e información acerca del volcado de memoria".

Se puede salir de un script de Perl con un código de error de tu eleción (de 0 a 255) usando exit.

Perl provee Más De Una Manera de - en una sola llamada - cargar y ejecutar un proceso hijo (spawn en inglés), pausar el script actual hasta que el proceso haya finalizado, y después continuar con la interpretación del script actual. Cualquier método que se use, tu encontrarás inmediatamente que la variable escalar incorporada $? ha sido poblada con la palabra de estado que fue devuelta por la terminación del proceso hijo. Tu puedes obtener el código de error tomando los 8-bits más altos de los 16 bits: $? >> 8.

La función system puede ser utilizada para invocar otro programa con los argumentos en una lista. El valor devuelto por system es el mismo valor con el cual $? es poblada:

my $rc = system "perl", "otroscript.pl", "foo", "bar", "baz";
$rc >>= 8;
print $rc; # "37"

Alternativamente, puedes usar las comillas inglesas invertidas `` para ejecutar un comando actual en la línea de comandos y capturar la salida estándar de ese comando. En contexto escalar, la salida completa es devuelta como una sola cadena de texto. En contexto de lista, la salida completa es devuelta como un array de cadenas de texto, cada una de ellas representando una línea de salida.

my $text = `perl otroscript.pl foo bar baz`;
print $texto; # "foobarbaz"

Este comportamiento podría observarse si otroscript.pl contiene, por ejemplo:

use strict;
use warnings;

print @ARGV;
exit 37;

Archivos y descriptores de archivos

Una variable escalar puede contener un descriptor de archivo, en vez de un número/cadena de texto/referencia o undef. Un descriptor de archivo es esencialmente una referencia a un lugar específico dentro de un archivo en particular.

Usa open para convertir una variable escalar en un descriptor de archivo. open debe ser suministrado con un modo. El modo < indica que deseamos abrir un archivo para lectura:

my $f = "texto.txt";
my $result = open my $fh, "<", $f;

if(!$result) {
    die "No se pudo abrir '".$f."' para lectura porque: ".$!;
}

Si es exitoso, open devuelve un valor verdadero. De lo contrario, devuelve un valor falso y un mensaje de error es incluido en la variable incorporada $!. Como visto anteriormente, tu siempre debes chequear que la operación open se haya completado exitosamente. Dado que este chequeo puede ser un poco tedioso, el idioma común es:

open(my $fh, "<", $f) || die "No se pudo abrir '".$f."' para lectura porque: ".$!;

Nota la necesidad por paréntesis alrededor de los argumentos en la invocación de open.

Para leer una línea de texto de un descriptor de archivo, usa la función incorporada readline. readline devuelve una línea completa del texto, con un salto de línea intacto al final (excepto para la línea final del archivo) o undef si alcanzaste el final del archivo.

while(1) {
    my $linea = readline $fh;
    last unless defined $line;
    # procesa la línea...
}

Para truncar ese salto de línea, usa chomp:

chomp $linea; # remueve el salto de línea que causa que el cursor se ha colocado en la siguiente línea

Nota que chomp actúa en $linea en el mismo lugar. $line = chomp $line es probablemente no lo que quieres.

También puedes utilizar eof para detectar que se ha llegado al final del archivo:

while(!eof $fh) {
    my $linea = readline $fh;
    # procesa la línea...
}

Sin embargo, sé cuidadoso al usar while(my $line = readline $fh), porque si $line resulta ser "0", el loop terminará tempranamente. Si quieres escribir algo como eso, Perl provee el operador <> que envuelve readline en una manera más segura. Esto es visto comunmente y es perfectamente seguro:

while(my $linea = <$fh>) {
    # procesa la línea...
}

Y hasta:

while(<$fh>) {
    # procesa $_...
}

La escritura a un archivo involucra primero abrir el archivo en un modo diferente. El modo > indica que deseamos abrir el archivo para escritura. (> borrará el contenido del archivo si existe y tiene contenido. Para agregar contenido a un archivo existente, usa el modo >>.) Después, simplemente suministra el descriptor del archivo como el elemento 0 a la función print.

open(my $fh2, ">", $f) || die "No se pudo abrir '".$f."' para escritura porque: ".$!;
print $fh2 "Las águilas abandonaron el nido";

Nota la ausencia de coma entre $fh2 y el siguiente argumento.

Descriptores de archivos son cerrados automáticamente después que están fuera de alcance. De lo contrario, usa close para cerrarlos:

close $fh2;
close $fh;

Tres descriptores de archivos existen como constantes globales: STDIN, STDOUT and STDERR. Estos se abren automáticamente cuando el script inicia. Para leer una línea de texto suministrada por el usuario:

my $line = <STDIN>;

Para esperar que el usuario solo presione Enter:

<STDIN>;

Al llamar <> sin descriptor de archivo, el script lee datos desde STDIN, o desde cualquier archivo mencionado en los argumentos cuando se ejecutó el script de Perl.

Como pudiste haber observado, print imprime a STDOUT por defecto si no descriptor de archivo es suministrado.

Pruebas de archivos (File tests)

La función -e es una función incorporada que verifica si un archivo existe.

print "what" unless -e "/usr/bin/perl";

La función -d es una función incorporada que verifica si un archivo es un directorio.

La función -f es una función incorporada que verifica si un archivo es un texto simple.

Estos son solo tres de una gran clase de funciones de la forma -X donde X es una letra minúscula o mayúscula. Estas funciones se llaman pruebas de archivos. Nota el signo de menos al frente. En una búsqueda de Google, el signo de menos indica la exclusión de resultados que contiene el término. Esto hace que una búsqueda de Google involucrando pruebas de archivos sea difícil! Solamente busca por "perl prueba de archivos" ("perl file test" en inglés).

Expresiones regulares

Expresiones regulares aparecen en muchos lenguajes y herramientas, a parte de Perl. La sintaxis de las expresiones regulares de Perl es básicamente la misma que en cualquier otro lugar, sin embargo las capacidades completas de las expresiones regulares en Perl son aterradoramente complejas y difícles de entender. El mejor consejo que puedo darte es evitar esta complejidad donde sea posible.

Las operaciones usadas para encontrar una determinada combinación de caracteres dentro de una cadena de texto se realizan con =~ m//. En contexto escalar, =~ m// devuelve verdadero en éxito, falso en fallo.

my $cadena = "Hola mundo";
if ($cadena =~ m/(\w+)\s+(\w+)/) {
    print "exito";
}

Paréntesis son usados para realizar sub-comparaciones. Después de una operación de comparación exitosa, las comparaciones exitosas son incluidas en las variables incorporadas $1, $2, $3, ...:

print $1; # "Hola"
print $2; # "mundo"

En un contexto de lista, =~ m// devuelve $1, $2, ... como una lista.

my $cadena = "limerancia verde ideas dormir furioso";
my @matches = $string =~ m/(\w+)\s+((\w+)\s+(\w+))\s+(\w+)\s+(\w+)/;

print join ", ", map { "'".$_."'" } @matches;
# prints "'limerancia', 'verde ideas', 'verde', 'ideas', 'dormir', 'furioso'"

Las operaciones de substitución se realizan con =~ s///.

my $cadena = "Buenos dias mundo";
$cadena =~ s/mundo/Vietnam/;
print $cadena; # "Buenos dias Vietnam"

Nota como el contenido de $cadena ha cambiado. Tu debes pasar una variable escalar en el lado izquierdo de la operación =~ s///. Si pasas una cadena literal, obtienes un error.

El indicador /g indica "comparación grupal".

En contexto escalar, cada invocación =~ m//g encuentra otra coincidencia después de la anterior, que devuelve verdadero en éxito, falso en fallo. Puedes accesar $1, $2,... en la manera usual. Por ejemplo:

my $cadena = "una tonelada de plumas o una tonelada de piedras";
while($cadena =~ m/(\w+)/g) {
  print "'".$1."'\n";
}

En un contexto de lista, un =~ m//g invoca todas la devoluciones de todas las coincidencias al mismo tiempo.

my @matches = $cadena =~ m/(\w+)/g;
print join ", ", map { "'".$_."'" } @matches;

Una invocación =~ s///g realiza un búsqueda/reemplazo global y devuelve el número de coincidencias. Aquí, nosotros reemplazamos todas las vocales con las letra "r".

# Inténtalo una vez sin /g.
$cadena =~ s/[aeiou]/r/;
print $cadena; # "rna tonelada de plumas o una tonelada de piedras"

# Una vez más.
$cadena =~ s/[aeiou]/r/;
print $cadena; # "rnr tonelada de plumas o una tonelada de piedras"

# Y haz el resto con /g
$cadena =~ s/[aeiou]/r/g;
print $cadena, "\n"; # "rnr trnrlrdr dr plrmrs r rnr trnrlrdr dr prrdrrs"

El indicador /i realiza comparaciones y substituciones insensible a mayúsculas.

El indicador /x permite que tus expresiones regulares contenga espacios (e.g., salto de línea) y comentarios.

"Hola mundo" =~ m/
  (\w+) # uno o más carácteres alfanuméricos
  [ ]   # un solo espacio literal, dentro de una clase de caracteres
  mundo # "mundo" literal
/x;

# devuelve verdadero

Módulos y paquetes

En Perl, módulos y paquetes son dos cosas diferentes.

Módulos

Un módulo es un archivo con la extensión .pm que puede incluirse en otro archivo Perl (script or módulo). Un módulo es un archivo de texto con exactamente la misma sintaxis de un script .pl de Perl. Un ejemplo de un módulo podría ser localizado en C:\foo\bar\baz\Demo\StringUtils.pm o /foo/bar/baz/Demo/StringUtils.pm, y ser leído como sigue:

use strict;
use warnings;

sub zombificar {
    my $palabra = shift @_;
    $palabra =~ s/[aeiou]/r/g;
    return $palabra;
}

return 1;

Dado que un módulo es ejecutado de arriba para abajo cuando se carga, necesitas devolver un valor verdadero al final para mostrar que fue cargado exitosamente.

Para que el interpretador de Perl pueda encontrarlos, directorios que contienen módulos de Perl deberían ser mencionados en tu variable de entorno PERL5LIB antes de llamar a perl. Cita el directorio raíz que contiene los módulos, en vez de citar los directorios de los módulos o los mismos módulos:

set PERL5LIB="C:\foo\bar\baz;%PERL5LIB%"

o

export PERL5LIB="/foo/bar/baz:$PERL5LIB"

Una vez que el módulo de Perl ha sido creado y perl sabe donde buscarlo, puedes usar la función incorporada require para buscarlo y ejecutarlo durante la ejecución de un script de Perl. Por ejemplo, al llamar require Demo::StringUtils, el interpretador de Perl escanea cada directorio citado en PERL5LIB, buscando un archivo llamado Demo/StringUtils.pm. Después que el modulo ha sido ejecutado, las subrutinas que fueron definidas en el módulo de repente están disponibles en el script principal. Nuestro script, por ejemplo, podría ser llamado main.pl y ser leído como sigue:

use strict;
use warnings;

require Demo::StringUtils;

print zombificar("quiero cerebros"); # "qrrrrr crrrbrrs"

Nota el uso del doble "dos puntos" :: como un separador de directorio.

Ahora un problema surge: Si main.pl contiene muchas llamadas de require, y cada uno de los módulos cargados contienen más llamadas de require, entonces se vuelve difícil rastrear la declaración original de la subrutina zombificar(). La solución a este problema es usar paquetes (packages en inglés).

Paquetes

Un paquete es un espacio de nombre (namespace en inglés) en el cual subrutinas pueden ser declaradas. Cualquier subrutina que tu declaras es implícitamente declarada dentro del paquete actual. Al inicio de la ejecución, tu estás en el paquete main pero puedes cambiar de paquete usando la función incorporada package:

use strict;
use warnings;

sub subrutina {
    print "universo";
}

package Comidad::Batata;

# no colisión:
sub subrutina {
    print "planta";
}

Nota el uso del doble "dos puntos" :: como un separador de directorio.

Cada vez que llamas una subrutina, implícitamente llamas una subrutina que está dentro del paquete actual. Alternativamente, puedes proveer explícitamente un paquete. Observa lo que pasa si continuamos con el anterior script:

subrutina();                  # "planta"
main::subrutina();            # "universo"
Comidad::Batata::subrutina(); # "planta"

Así que la solución lógica al problema descripto anteriormente es modificar C:\foo\bar\baz\Demo\StringUtils.pm o /foo/bar/baz/Demo/StringUtils.pm como en lo siguiente:

use strict;
use warnings;

package Demo::StringUtils;

sub zombificar {
    my $palabra = shift @_;
    $palabra =~ s/[aeiou]/r/g;
    return $palabra;
}

return 1;

Y modificar main.pl como sigue:

use strict;
use warnings;

require Demo::StringUtils;

print Demo::StringUtils::zombificar("quiero cerebros"); # "qrrrrr crrrbrrs"

Ahora lee lo siguiente atentamente.

Los paquetes y módulos son dos características completamente distintas y separadas del lenguaje de programación Perl. El hecho de que ambos usen el delimitador doble de "dos puntos" es una maniobra de distracción. Es posible cambiar varios paquetes a través de la ejecución de un script o módulo, y es posible usar la misma declaración de un paquete en varios lugares en múltiples archivos. Al llamar require Foo::Bar, el script no busca y carga un archivo con una declaración package Foo::Bar en alguna parte, ni tampoco carga subrutinas localizadas en el espacio de nombre Foo::Bar. Al llamar require Foo::Bar, el script simplemente carga un archivo llamdo Foo/Bar.pm, el cual no necesita tener ningún tipo de declaración de paquete dentro de si, y que de hecho podría declarar package Baz::Qux y otras cosas sin sentido dentro de si mismo.

De igual manera, una invocación de subrutina Baz::Qux::processThis() no necesita necesariamente haber sido declarada dentro de un archivo llamado Baz/Qux.pm. Podría haber sido declarada literalmente en cualquier parte.

La separación de estos dos conceptos es una de las características más estúpidas de Perl, y tratarlos como conceptos diferentes invariablemente resulta en código caótico y desquiciado. Afortunadamente para nosotros, la mayoría de programadores de Perl obedecen las dos siguientes reglas:

  1. Un script de Perl (archivo .pl) debe siempre contener exactamente cero declaraciones package.
  2. Un módulo de Perl (archivo .pm) debe siempre contener exactamente una declaración package, correspondiente exactamente a su nombre y localización. E.g. el módulo Demo/StringUtils.pm debe iniciar con package Demo::StringUtils.

Debido a esto, en la práctica tu encontrarás que muchos "paquetes" y "módulos" producidos por terceras identidades confiables pueden ser referidos de estas maneras intercambiablemente. Sin embargo, es importante que no lo tomes por sentado dado que un día encontrarás código creado por un desquiciado.

Programación orientada a objetos en Perl

Perl no es un gran lenguaje para la programación OO (Orientada a Objetos). Las capacidades de Perl para la programación OO fueron injertadas posterior a su creación y esto se nota.

Un pequeño ejemplo clarificará esto. Un módulo llamado Animal.pm que contiene una clase Animal se lee como sigue:

use strict;
use warnings;

package Animal;

sub come {
    # El primer argumento es siempre el objecto sobre el que se actúa.
    my $self = shift @_;

    foreach my $comida ( @_ ) {
        if($self->comestible($comida)) {
            print "Comiendo ", $comida;
        } else {
            print "No se puede ", $comida;
        }
    }
}

# Asumamos que un Animal puede comer cualquier cosa
sub comestible {
    return 1;
}

return 1;

Y podríamos usar esta clase en la siguiente forma:

require Animal;

my $animal = {
    "extremidades"   => 4,
    "color"          => "marrón",
};                       # $animal es una referencia ordinaria a un hash
print ref $animal;       # "HASH"
bless $animal, "Animal"; # Después de bendecirlo (bless), $animal es un objeto de la clase "Animal"
print ref $animal;       # "Animal"

Nota: Cualquier referencia puede ser bendecida en relación a una clase. Depende de ti que (1) el referente pueda actualmente ser usado como una instancia de esta clase y que (2) la clase en cuestión exista y haya sido cargada.

Se puede todavía trabajar con el hash original en la manera usual:

print "Animal tiene ", $animal->{"extremidades"}, " extremidad(es)";

Pero ahora puedes también invocar métodos en el objeto usando el mismo operador ->, así:

$animal->come("insectos", "curry", "eucalipto");

Esta invocación final es equivalente a lo siguiente Animal::come($animal, "insectos", "curry", "eucalipto").

Constructores

Un constructor es un método de una clase que devuelve un nuevo objeto cuando se invoca. Si quieres uno, declaras uno. Puedes usar cualquier nombre que prefieras. Para los métodos de una clase, el primer argumento que se pasa no es un objeto pero el nombre de la clase. En este caso, "Animal":

use strict;
use warnings;

package Animal;

# La convención es siempre usar nuevo (new en inglés)
# como el nombre del constructor. Sin embargo, no es un requerimiento.

sub nuevo {
    my $clase = shift @_;
    return bless { "extremidades" => 4, "color" => "marrón" }, $clase;
}

# ...etc.

Y después lo usas así:

my $animal = Animal->nuevo();

Herencia

Para crear una clase que herede de una clase padre, usa use parent. Supongamos que hemos creado una subclase Koala usando la clase padre Animal. La subclase está localizada en Koala.pm:

use strict;
use warnings;

package Koala;

# Hereda de Animal
use parent ("Animal");

# Anulando/modificando un método
sub comestible {
    my $self = shift @_; # No se usa. Puedes colocar "shift @_;" aquí
    my $comida = shift @_;
    return $comida eq "eucalipto";
}

return 1;

Y algún código de ejemplo:

use strict;
use warnings;

require Koala;

my $koala = Koala->nuevo();

$koala->come("insectos", "curry", "eucalipto"); # come solo eucalipto

Este última llamada del método trata de invocar Koala::come($koala, "insectos", "curry", "eucalipto"), pero una subrutina come() no está definida en el paquete Koala. Sin embargo, dado que Koala tiene una clase padre Animal, el interpretador de Perl intenta llamar Animal::come($koala, "insectos", "curry", "eucalipto"), lo cual funciona. Nota como la clase Animal fue cargada automáticamente por Koala.pm

Debido a que use parent acepta una lista con los nombres de las clases padres, Perl soporta herencias múltiples, con todos los beneficios y horrores que esto implica.

Bloques BEGIN

Un bloque BEGIN es ejecutado tan pronto como perl ha finalizado parseando ese bloque y hasta antes que haya parseado el resto del archivo. Es ignorado al tiempo de ejecución:

use strict;
use warnings;

print "Esto es imprimido segundo";

BEGIN {
    print "Esto es imprimido primero";
}

print "Esto es imprimido tercero";

Un bloque BEGIN es siempre el primero en ser ejecutado. Si creas múltiples bloques BEGIN (no lo hagas), ellos son ejecutados en orden de arriba para abajo como el compilador los encuentra. Un bloque BEGIN siempre es ejecutado primero si se coloca en el medio del script (no hagas esto) o al final (o esto). No intervenga con el flujo y orden natural del código. Coloca un bloque BEGIN al unicio!

Un bloque BEGIN es ejecutado tan pronto como el bloque ha sido parseado. Una vez que esto termina, el parseado continúa al final del bloque BEGIN. Solo cuando el script completo o módulo ha sido parseado, el código restante fuera de los bloques BEGIN es ejecutado.

use strict;
use warnings;

print "Esta sentencia 'print' es parseada exitosamente pero nunca es ejecutada";

BEGIN {
    print "Esta es imprimida primero";
}

print "Esta, también, es exitosamente parseado pero nunca ejecutada";

...e4h8v3oitv8h4o8gch3o84c3 porque hay un gran error aquí.

Dado que ellos son ejecutadas al tiempo de compilación, un bloque BEGIN colocado dentro de un bloque de condición todavía será ejecutado primero, aunque la condición evalúe a falso y a pesar de que la condición no haya sido evaluada y que de hecho, podría nunca ser evaluada.

if(0) {
    BEGIN {
        print "Esto será imprimido definitivamente";
    }
    print "A pesar de que esto no será";
}

Nunca pongas un bloque BEGIN dentro de condiciones! Si quieres hacer algo condicionalmente al tiempo de compilación, necesitas poner la condición dentro del bloque BEGIN:

BEGIN {
    if($condicion) {
        # Haz esto.
    }
}

use

Okay. Ahora entiendes el comportamiento peculiar y la semántica de paquetes, módulos, métodos de clases y bloques BEGIN, puedo explicar la función excesivamente común use.

Las tres siguientes sentencias:

use Lemur ("salta", "trepa");
use Lemur ();
use Lemur;

son respectivamente equivalente a:

BEGIN {
    require Lemur;
    Lemur->import("salta", "trepa");
}
BEGIN {
    require Lemur;
}
BEGIN {
    require Lemur;
    Lemur>import();
}

Exporter

La manera más común de definir un método import() es heredarlo del módulo Exporter. Exporter es un módulo incluido en Perl por defecto y una de las características de facto del lenguaje de programación Perl. En la implementación Exporter de import(), la lista de argumentos que pasas es interpretada como una lista de nombres de subrutinas. Cuando una subrutina es import()ada, se vuelve disponible en el actual paquete, tanto como en su paquete original.

Este concepto es mejor de entender con un ejemplo. Así es como Lemur.pm luce:

use strict;
use warnings;

package Lemur;

# Inherit from Exporter
use parent ("Exporter");

sub salta   { print "inch inch";   }
sub come    { print "chomp chomp"; }
sub trepa   { print "bloop bloop"; }

our @EXPORT_OK = ("salta", "come");

return 1;

La variable paquete @EXPORT_OK debería contener una lista con los nombres de las subrutinas.

Otra pieza de código podría entonces import()ar estas subrutinas por nombres, típicamente usando la sentencia use:

use strict;
use warnings;
use Lemur ("salta");

salta(); # "inch inch"

En este caso, el paquete actual es main, así que salta() invoca a main::salta(), la cual (porque fue importada) está enlazada a Lemur::salta().

Nota: Sin importar el contenido de @EXPORT_OK, cada método puede ser invocado en la forma "extendida":

use strict;
use warnings;
use Lemur (); # no subrutinas nombradas, no llamada a import() hecha

# y todavía...
Lemur::salta();   # "inch inch"
Lemur::come();    # "chomp chomp"
Lemur::trepa();   # "bloop bloop"

Perl no tiene métodos privados. Por lo general, un método que está supuesto a ser privado se le coloca un guión bajo (o dos) al frente de lo que sería su nombre normal.

@EXPORT

El módulo Exporter también define una variable paquete llamada @EXPORT, la cual también es poblada con una lista con los nombres de las subrutinas.

use strict;
use warnings;

package Lemur;

# Hereda de Exporter
use parent ("Exporter");

sub salta   { print "inch inch";   }
sub come    { print "chomp chomp"; }
sub trepa   { print "bloop bloop"; }

our @EXPORT = ("salta", "come", "trepa");

return 1;

Las subrutinas nombradas en @EXPORT son importadas si import() es invocada sin ningún argumento, que es lo que pasa aquí:

use strict;
use warnings;
use Lemur; # invoca import() sin argumentos

salta();   # "inch inch"
come();    # "chomp chomp"
trepa();   # "bloop bloop"

Sin embargo, nota como estamos en una situación donde, sin ninguna pista, no sería fácil decir donde salta() fue originalmente definida. La moral de la historia es doble:

  1. Al crear un módulo que hace uso de Exporter, nunca uses @EXPORT para exportar subrutinas por defecto. Siempre haz que el usuario invoque subrutinas de la forma "extendida" o import()alas explícitamente (usando e.g. use Lemur ("salta"), que es una gran pista para buscar en Lemur.pm por la definición de salta()).

  2. Al hacer uso de use y un módulo que hace uso de Exporter, siempre nombra explícitamente las subrutinas que quieres import()ar. Si no quieres import()ar ninguna de las subrutinas y deseas referirte a ellas en la forma "extendida", debes proveer una lista vacía explícita: use Lemur ().

Notas misceláneas

Y esas son dos horas y medias.

Back to Things Of Interest