Un mal día, mi jefe llegó preguntando por un forma de analizar unos
logs de un programa que utilizamos. Primero una cosa sencilla, y
mi tonta cabeza pensó:
Procesamiento de texto + Algoritmo simple = Perl
No lo vuelvo a hacer.
En ese momento pareció y fue buena idea, detectar un patrón de texto
en un archivo, organizar un poco el texto y limpiarlo, parecía y fue
factible, cuestión de minutos, más tiempo en recordar Perl, afinar la
expresión regular, ver unos cuantos ejemplos sobre arreglos (pero se
veía que llegaba a cosas oscuras) y listo.
Luego al ver el resultado y que había áreas de oportunidad, el
requerimiento creció considerablemente; se encontró un error y
seguimos para adelante.
El error fue resuelto, no sin sus problemas. En mi cabeza rondaban
distintas formas de resolverlo, alguien con idea en el problema del
análisis de texto podrá recordar estos problemas alguna vez:
Teoricamente el archivo tiene la forma:
[Una linea]
[Una linea]
...
Pero en realidad tenía la forma:
[Una linea]
[Una linea][Una linea]
[Una linea][Una mentirosa linea][Una linea]
[Una
linea]
[Una
linea][Una linea]
...
Y combinaciones peores que esa. Así que como opciones encontré:
- Considerar que el salto de linea no fuera mi separador de cadenas.
- Que mis expresiones regulares fueran multi-lineas.
- Otro par que afortunadamente ya no recuerdo.
Al final, después de pensarle un poco dí con una solución
correcta. Pero recae muy poco en el poder de Perl de manejar
expresiones regulares, tanto que podría haberlo escrito en Python
sin ningún contratiempo, al menos no hubiera sido peor que lo que
había hecho en Perl.
Pero hasta ese momento puedo presumir de que Perl había realizado su
tarea sin muchos problemas. El problema fue cuando quise implementar
los requerimientos extras.
En cuanto a mis requisitos para el lenguaje, una vez diseñada mi
solución (y que decidí limpiar un poco el código) son:
- Arreglos de Hash.
- Arreglos de Arreglos de Hash.
- Funciones que reciben valores por referencia.
- Referencia a elementos de arreglos o de Hash.
Estas cosas suenan de lo más natural e inocencentes y han probado ser
una desgracia infinita para mí. No por que no pueda resolver mi
problema, sino por que no encuentro la forma correcta de
hacerlo. Me centraré en el problema de las referencias para
ejemplificar. Tengo un archivo de pruebas que voy generando mientras
voy aprendiendo gramática nuevas (Sí, mientras voy aprendiendo formas
nuevas de hacer lo mismo sigh).
Al inicio del archivo declaro mis buenas intenciones, la prueba es
clásica, declaro una variable ($a) luego hago una referencia a ella
$b y modifico el valor de la variable via su referencia. No me voy a
meter en lo feo que se ve \$, yo creo que esto aceptable en un
lenguaje como Perl:
# Trying to learn how to use references in perl
# Lesson 1: Make reference with \$ and dereference with $$
$a = "pp";
$b = \$a;
$$b = "qq";
print "\$a changed via \$\$b $a\n";
Después intento hacer lo mismo via referencias, tomo la referencia de
$a via \$a sin variables intermedias, está todo bien, cuando
declaro la variable con my, no pongo $$, sólo en el resto de los
casos. Esto es coherente, puedo pasar de mi lección 1 a la lección 2
sin problemas:
# Lesson 2: This works for functions too in the same way \$ and $$
sub t {
my $p = shift;
$$p = "rr";
}
t(\$a);
print "\$a changed via t(\\\$a) $a\n";
Por alguna extraña razón en mi script de trabajo, necesito utilizar
prototipos (que por cierto no pudieron ser más feos) así que me pongo
a probar como hacer prototipos. Y aquí es donde surge la duda, ¿qué
recibe la función? Siendo coherente con la lección 2, una referencia a
escalar (\$), no un escalar realmente, pero aparentemente las
referencias a escalaras son... escalares. Así que yo no me fuí con el
ejemplo anterior y jugué un poco, después de esto obtuve este código
funcional:
# Lesson 3a: with prototypes
sub u ($);
u(\$a);
sub u ($) {
my $p = shift;
$$p = "ss";
}
print "\$a changed via u(\\\$a) $a, with prototype u(\$)\n";
Poniendo atención a la linea de asignación en la función `$$p =
"ss"`. Por que aquí viene el momento en que decimos
Bienvenido a Perl:
# Lesson 3b: same to the one above but using declaring the reference
# in the function no in the call
sub v (\$);
v($a);
sub v (\$) {
my $p = shift;
$$p = "tt";
}
print "\$a changed via v(\$a) $a, with prototype v(\\\$)\n";
Veamos, utilizando una referencia ahora sí, entonces, resulta que $p
tiene una referencia que con $$p se puede "desreferenciar". Bien
entonces si \$a es una referencia y $a es un escalar, ambos
enviados a una función, $$p, sirve para acceder al mismo valor
en ambos casos? ¿\$a y $a son sinónimos? No, pero me dan el mismo
resultado, ¿cómo se supone que debo hacerlo? Buscando esta respuesta
se me ocurrió otra combinación:
# Lesson 3c: same to the others, not declaring reference in the call
# nor the declaration, just use it via $$
sub w ($);
w($a);
sub w ($) {
my $p = shift;
$$p = "tt";
}
print "\$a changed via w(\$a) $a, with prototype w(\$)\n";
Aquí es donde nos paramos de la mesa y mejor nos vamos a otra cosa. La
función espera un scalar, recibe un scalar, y mágicamente, con la
soberana autoridad de hacer lo que se le da la gana "desreferencia"
un scalar ¿? Sí. Eso hizo y tengo un nuevo valor para $a.
¿Cuál es la forma correcta de hacerlo?
En este momento me parecía que la última, ya que aunque no era
explicita -ni mucho menos- era la menos fea (la \ es horrible). Aunque
es la peor en términos de su significado (no dice nada al respecto de
que se esta haciendo). Pero cuando intenté extender esto a arreglos me
dí cuenta que esa era mi peor opción.
Eso lo pondré en otro episodio más de: No importa cuanto procesamiento
de texto te prometan, si un lenguaje programación no es capaz de
mantener una sintaxis estable y elegante para estructuras de datos
complejas gira la página y vete al siguiente.