En la publicación anterior revisamos algunas de las estructuras de datos predefinidas en Perl. En esta ocasión cubriremos algunas otras y conversaremos sobre el contexto en Perl.
En singular nuevamente
Dedicaremos algunas lineas a conversar sobre un elemento escalar bastante diverso y poco comentado en la publicación anterior: las referencias. En Perl diversos elementos se almacenan en referencias, y su manejo al igual que la referenciación-derreferenciación tiene distinción en la sintaxis.
Para la creación de referencias hay ciertas estructuras para distinguir una referencia de otra, en algunos casos.
my $array_ref = [1,2,3]; #Creando referencia a array my $hash_ref = {1 => 1, 2 => 2}; #Creando referencia a hash my $code_ref = sub { return 1; }; #Creando referencia a un procedimiento my $regex_ref = qr/aeiou/; #Creando referencia a una expresión regular! my $scalar = "No soy una referencia naturalmente"; my $scalar_ref = \$scalar #Esto se puede hacer con cualquier tipo de elemento $ref_ref = \$scalar_ref #Una buena referencia a una referencia my $myclass_ref = bless $hash_ref, "MyClass"; #Ahora el hash es un objeto! my $glob_ref = *STDIN; #Un ser mágico llamado typeglobs, más sobre ellos luego my $io_ref = *STDIN{IO} #La referencia de entrada/salida de la entrada estandar
Muchos tipos de referencia en efecto, pero notemos algunas características. Las referencias a arreglos y hash son de declaración particular, utilizando los paréntesis con los cuales se indican los elementos a los cuales se quiere acceder, una excelente regla nemotécnica.
Antes de comentar la siguiente referencia se debe comentar una característica común a los lenguajes modernos y aprovechada por Perl. Toda variable o referencia no es eliminada al salir del bloque donde es declarada si existe alguna variable fuera del bloque que aún la referencie.
Ahora, manejamos referencias a procedimientos de código en Perl. Es decir, los procedimientos son ciudadanos de primera clase en Perl. Sumando esto a lo anteriormente descrito tenemos la posibilidad de utilizar las referencias a código para la generación de clausuras (http://en.wikipedia.org/wiki/Closure_(computer_programming)).
Las expresiones regulares igualmente pueden ser almacenadas en referencias para reuso en múltiples secciones de código. Una forma clásica de manejar referencias es tomar variables y almacenar su referencia. Esto se realiza mediante el caracter “\”.
Toda referencia en Perl puede ser convertida en un objeto, esto se combina con el concepto de Package para general la estructura de clases. El método constructor de la clase llama al método bless y retorna una referencia del tipo de la clase, capaz de llamar a los métodos de la misma por supuesto.
Ahora, definir la referencia no es suficiente, debemos poder acceder a los valores de la referencia posteriormente. La regla de derreferenciación se aprovecha nuevamente de los sigilos para lograr simplicidad.
my @array = @$array_ref; #Es permitido también my @array = @{$array_ref}; #También my $string = "array_"; my $string2 = "ref"; my @array = @{$string.$string2}; #Por otra parte &{$code_ref}(); #Nótese el sigilo de referencia de código
La regla es simple, realizar la derreferenciación es anteponer a la variable con la referencia el sigilo correspondiente al tipo de dato que queremos obtener. Perl también permite la derreferenciación indirecta, esto es, con una expresión que retorne el nombre de la variable que contiene la referencia y el sigilo correspondiente se obtiene el valor deseado. Está de más decir que esta regla aplica para cualquier elemento.
Existe otro operador de derreferenciación utilizado fundamentalmente en hash, arreglos y procedimientos. Este está inspirado en el operador de derreferenciación de C/C++ “->” (Al decir inspirado en realidad queremos decir que es el mismo operador).
my $nested_ref = { array => [1,2,3], hash => { 1 => "uno", 2 => "dos" }, code => sub { return "hello world"; } }; #Esta es la primera posibilidad print $nested_ref->{array}->[0]; # 1 #Pero una posibilidad más corta print $nested_ref->{array}[0]; # 1 #Siendo válido para todos los casos print $nested_ref->{hash}{1}; # uno print $nested_ref->{code}() # hello world
Es de notar la ausencia del operador de derreferenciación entre la primera derreferenciación y las sucesivas. A fin de simular con mayor facilidad las estructuras anidadas, como por ejemplo las matrices, se permite obviar el operador de derreferenciación entre los distintos índices.
Plural, singular, verbo, global: ¡Tabla de símbolos!
Perl es sobre flexibilidad, sobre libertades. Para algunos la frontera final de la libertad es poder cambiar a voluntad el entorno de ejecución, ¿Quién sería un lenguaje para privarlos de esa posibilidad? Esto es logrado mediante una estructura llamada Typeglob. El typeglob simula la tabla de símbolos almacenando, para cada nombre, la referencia correspondiente en el paquete indicado para el tipo de referencia al que corresponda el símbolo ($,@,&,%,…).
Todo paquete genera su propia tabla de símbolos, al igual que en todo lenguaje tradicional. La diferencia fundamental es la capacidad de modificación de la misma. Veamos
package MyPackage; sub hello { return "Hola desde MyPackage"; } package main; print MyPackage::hello(); # Hola desde MyPackage *MyPackage::hello = sub { return "No más hola desde MyPackage, desde main" }; *MyPackage::x = \5; print MyPackage::hello(); # No más hola desde MyPackage, desde main print $MyPackage::x; # 5
Podemos notar que desde el paquete main sobreescribimos el método hello del paquete MyPackage, y que no sólo esto, sino que agregamos un símbolo nuevo al paquete. Tomemos en cuenta que las clases se definen mediante paquetes y se observa la gran relevancia que toman los typeglobs: ¡Clases con métodos mutables en tiempo de ejecución! Tómense un tiempo adicional a esta lectura para explorar con esta estructura, es bastante útil y poderosa. Representa un beneficio enorme para el aprovechamiento de un concepto en general críptico y misterioso como lo es la mutación de código de una forma conocida y documentada desde hace décadas.
Tomemos una pausa
¡Whew! Bastantes elementos sintácticos fuertes hemos cubierto en esta publicación, y no estamos siquiera cercanos a cubrirlos todos. No obstante tenemos bastante para definir prácticamente cualquier elemento en una declaración en lenguaje natural: Sustantivos, condicionales y uniones/disyunciones (if, unless, or, and), verbos (en caso que no se vea tan claro hablamos de los procedimientos), pronombres, elementos en plural y en singular, tendríamos sin definir los adjetivos, pero eso lo guardaremos para otra publicación.
Pero claro, ¿que hay sobre al comportamiento contextual de nuestro lenguaje? Naturalmente en Perl no podríamos olvidar esto, mucho menos lo harían los desarrolladores del lenguaje.
Todo depende desde dónde lo mires
No es lo mismo para nosotros hablar sobre Pedro, Juan y María, individualmente que hablar sobre “ellos” como grupo. En un lenguaje donde queremos simular lo natural no podemos olvidar el contexto. En Perl se manejan fundamentalmente tres contextos: Escalar, lista y nulo. A su vez el contexto escalar se suele dividir en tres más: numérico, string y “no me importa”. Conversemos un poco sobre ellos.
Contexto nulo
Abordamos este en principio dado que es simple de explicar. El contexto nulo es ese donde se descarta lo obtenido, simplemente, lo que genere la expresión es ignorado. Por supuesto nos referimos a resultados, a los tipos del retorno, siquiera interesa un retorno. Si un procedimiento que altera un elemento que trasciende a su ambiente es ejecutado el resultado se conserva, por ejemplo, en el caso de métodos sobre un objeto y que afecten el mismo. Observemos algunos casos del contexto nulo
5; (1,2,3); hello();
Es importante tener en cuenta que el contexto nulo requiere que no se realice una operación, en caso contrario no se maneja este contexto.
Contexto de lista
Hemos visto el concepto de lista en Perl durante la publicación anterior, la colección de elementos separados por comas o generados por qw. El contexto de lista es generado al tratar de asignar un operador a un elemento de lista. Claro está que esto va mucho más allá de realizar asignación a una variable arreglo o hash.
my @arreglo = (1,2,3); # Una asignación de lista simple my %hash = (1,2,3,4); # 1 => 2, 3 => 4 my ($uno,$dos) = @arreglo; # Asignación de lista sobre valor izquierdo
Es importante notar una diferencia fundamental con lenguajes tradicionales, la asociación a contexto de lista en el lado izquierdo de la asignación. Esta asignación es voraz desde la izquierda, es decir, consume y asigna lo máximo posible a una variable antes de seguir con la siguiente
my (@all,$none) = (1,2,3,4); # @all = 1,2,3,4 $none = undef
Contexto Escalar
El tercer contexto en Perl es uno de los más complejos, el escalar. Su complejidad no se encuentra en su comprensión, sino en su operabilidad, y lo que ocurre al llevar elementos de otros contextos a él. En sí mismo los elementos escalares son esos elementos en singular que hemos conversado: strings, números, referencias. No nos detendremos a observar el comportamiento de este contexto, bastante ha sido demostrado. Vamos a conversar sobre la coerción entre este contexto y el de lista.
#Coerción arreglo a escalar my @array = (0,5,4); my $s = @array; # Retornará 3 al imprimir #Coerción lista a escalar my $d = ("string","list"); # Retornará list al imprimir #Coerción mediante operación print @array + 1; # 4
Vemos que manejamos distintos esquemas de coerción y comportamientos al pasar entre distintos elementos de contexto lista a escalar. En el primer ejemplo se observa que convertir un arreglo a escalar provoca que se retorne el tamaño del arreglo. Este caso se expande para los hash, sólo que en este caso devuelve una fracción que indica posibilidades de colisión en un hash. Los hash son arreglos de listas enlazadas. Una función convierte la llave en un número que representa la posición del arreglo donde se almacenará el elemento. El denominador de la fracción devuelta al convertir el hash a escalar representa la cantidad de posiciones totales y el numerador la cantidad de posiciones ocupadas.
Ahora, cuando convertimos una lista a contexto escalar, en lugar de un arreglo, el comportamiento es distinto. En este caso se descartan todos los elementos excepto el último.
Finalmente, al tratar con operadores podemos notar que ellos funcionan igualmente como agentes de coerción. Los operadores tienen distintos contextos sobre los cuales se manejan, y convierten a los elementos que no pertenecen a su contexto en su equivalente. Esto representa una facilidad y un desafío, puesto que es necesario conocer qué operadores se manejan con cada contexto.
Ahora, dentro del contexto escalar Perl se comporta de forma diferente en base a qué operador se utilice, generando una división entre contexto escalar numérico, string o “no me importa”.
print ("casa" + 1); # 1 print ("1 casa" + 1); # 2 print (1 . 2); # 12
Es de notar que según el operador cada elemento se comporta de forma distinta, pero natural a su operador. No esperamos que “casa” tome algún valor numérico distinto a nada en el caso numérico estándar. Pero “1 casa” sigue siendo un cardinal, por lo cual se convierte en 1. Ahora, si tratamos con una concatenación es mucho más razonable que el número sea convertido en su equivalente string en lugar de operaciones inusuales como convertir el número en el caracter equivalente de la codificación. Estas reglas pueden extrapolarse con poco riesgo a muchos otros operadores, recuerden, todo se trata de que se sienta natural.
Existen operadores de contexto escalar los cuales no realizan conversión, con una razón simple, no les importa. Esto puede sonar algo pedante de parte de esos operadores, pero la realidad es que a efectos de su operación el tipo de dato escalar es irrelevante. Un caso simple de esto es la asignación escalar que, al no hacer uso del contenido, ignora su contexto. Los operadores lógicos son también operadores “no me importa”, dado que simplemente evaluan por los valores verdadero o falso correspondientes, sin convertir nada más.
Con esto concluimos la leve comprensión de esta compulsión con el lenguaje natural que maneja Perl. En futuras publicaciones cubriremos algunos complementos adicionales del lenguaje en relación con esto. De momento finalizamos la publicación y les agradecemos por haber llegado hasta tan lejos en esta larga publicación (no bromeamos, es una publicación realmente larga), hasta próximas lecturas.