lunes, 31 de agosto de 2009

La magia de Perl

[Google english translation]
Una de las características que hacen de perl una navaja suiza (por cierto afilada) son las construcciones mágicas del lenguaje, que permiten escribir micro programas rápida y fácilmente, y es una de las razones por las cuales el lenguaje tiene fama de incomprensible.
Aunque el uso de estas construcciones están contraindicadas en la programación de sistemas, son de gran utilidad en el día a día de los administradores de sistemas y programadores, por ello voy a explicar las más comunes, para que puedas apreciar su potencia y entender los scripts que se crucen en tu camino.

El operador de lectura

En Perl se puede leer una línea de la entrada estándar de la siguiente manera:

my $linea = readline(STDIN);
Sin embargo también existe el operador de lectura <ARCHIVO> que permite leer una línea así:

my $línea = <STDIN>;
adicionalmente si el archivo es STDIN se puede omitir el nombre en el operador, quedando simplemente:

my $linea = <>;
Cuando enfaticé "una línea" en los párrafos anteriores lo hice porque en Perl no se leen líneas, en realidad se leen registros, claro que por defecto los registros están definidos como texto terminado por fines de línea, que a su vez tienen un significado diferente en cada plataforma, por ejemplo en Unix el fin de línea es "\n", mientras que en Windows es "\r\n".

El acumulador o Variable Implícita ($_)

Es una variable global que se utiliza implícitamente, sobre todo en las operaciones mágicas, su nombre viene de la similitud con el acumulador en Ensamblador que se utiliza implícitamente, aunque es mejor llamarla "Variable Implícita", el nombre de esta variable en Perl es: "$_".
Uno de los usos más comunes de la variable implícita es en la prueba de expresiones regulares, normalmente cuando quieres saber si el contenido de una variable concuerda con una expresión regular haces:

if ( $texto =~ m/lo que se busca/ ) { ...
Pues bien, si el texto se encuentra en $_ entonces se puede hacer:

if ( m/lo que se busca/ ) { ...
Además $_ se utiliza en algunas funciones de la librería como split y print, por ejemplo:


@campos = split( /,/, $texto );
@campos = split /,/;
La primera línea pica el contenido de la variable $texto (por las comas) mientras la segunda hace lo mismo utilizando implícitamente el contenido de $_. Algo similar sucede con print. Por cierto, ¿se notó que la segunda llamada no tiene paréntesis?, eso es producto de que en Perl esos paréntesis son opcionales (pero dejaré eso para otro artículo).

El ciclo mágico

Cuando se combina $_ el operador de lectura <> y un ciclo while tenemos el ciclo mágico de Perl:

while ( <> ) {
    print;  # se imprime $_ implícitamente 
}
Este ciclo lee de la entrada estándar mientras haya líneas que leer, y el resultado de cada lectura queda en $_, en efecto el programa anterior se parece mucho a cat(1) de Unix, supongamos que tenemos este programa instalado con el nombre plcat, entonces:

$ plcat < archivo.txt
$ plcat < fuente.txt > destino.txt
$ ls -R / | plcat
Hacen exactamente lo mismo que cat(1), pero además hace más magia, así que:

$ plcat archivo.txt
$ plcat archivo.txt otro.txt
Incluso el argumento "-" de cat funciona igual!:

$ plcat archivo.txt - otro.txt

El ciclo mágico implícito

El ciclo mágico puede omitirse si perl se invoca con alguna de las opciones -p ó -n, la primera envuelve todo el código de Perl en el ciclo que antes vimos, con el print al final, la segunda hace lo mismo pero omite el print. Así que el programa anterior se puede escribir en una sola línea de perl:

$ perl -ne 'print' archivo.txt
Aunque como -p también incluye el print podemos hacer:
$ perl -pe '' archivo.txt

Un ejemplo práctico

Una forma de escribir grep en Perl sería:

#!/usr/bin/env perl
my $patron = shift(@ARGV);
while (<>) {
    print "$ARGV:$.: $_" if m/$patron/; 
}
La variable $ARGV contiene el nombre del archivo actualmente abierto por el ciclo mágico, y la variable $. contiene el número de la última línea leída. Todo esto se puede hacer directamente desde la línea de comandos:

$ perl -ne 'print "$ARGV:$.: $_" if m/lo que busco/'
Y después de saber como funcionan los operadores lógicos en Perl, puedo hacerlo así también:

$ perl -ne '/lo que busco/ && print "$ARGV:$.: $_"'

El separador de registros

Ya mencione que el concepto de línea en Perl en realidad significa registro, y que los registros se definen por defecto para coincidir con el fin de línea del sistema operativo. La variable global que configura como se interpretan los registros en Perl es $/, así que si se necesita procesar un archivo separado por una cadena particular solamente se asigna esa cadena a $/.
Esta variable tiene valores especiales para lograr diversos efectos, el primer valor especial es indefinido, cuando $/ = undef, el registro es todo el archivo, así que:

$todo = readline(STDIN);

Lee todo el archivo en la variable, se imaginarán que si el archivo es un DVD probablemente te quedarás sin memoria antes de que el programa termine.
Otro valor interesante es la cadena vacía, que permite leer registros de varias líneas delimitados por líneas en blanco, el siguiente programa lee un archivo de paquetes de Debian paquete por paquete imprimiendo el nombre de cada paquete:

#!/usr/bin/env perl
$/ = '';
while (<>) {
    /^Package:\s+(.*)/m or die "Error de sintaxis";
    print "$1\n";
}

Y por supuesto su correspondiente programa de una línea:

$ perl -ne '/^Package:\s+(.*)/m && print "$1\n"'

Recomendaciones finales

El propósito de este artículo es dar a conocer técnicas importantes que ayudan a resolver problemas puntuales, sin embargo, casi todo lo aquí expuesto debe evitarse para programas que sean de más de una docena de líneas o cuando se pretenda instalar un programa como herramienta en una organización.
Para programas grandes ó duraderos:
  • Evita $_ excepto cuando no haya más remedio, o en pequeños trozos de código donde esté topicalizada y aún así es mejor usar una variable local (declarada con my), si no sabes que es eso de la topicalización no uses $_.
  • Usa preferiblemente readline() en vez del operador de lectura, porque hay mas chance de que un novato entienda el programa.
  • No uses $/ pues cambia el comportamiento de todos los archivos abiertos en perl, esto es común para algunas otras variables que configuran el ambiente operativo en perl y que puedes consultar con el comando: perldoc perlvar
Suficiente magia por hoy.

lunes, 24 de agosto de 2009

Perl arcaico

[English translation]
Hace un par de dias me tocó atender un proveedor que vino a ofrecer sus servicios para el desarrollo de una aplicación web.
Como alguno de los participantes de la organización tuvo que manejar un imprevisto aproveché para iniciar un pequeña investigación durante la charla informal: "¿Que herramientas de desarrollo utilizan en su empresa?".
En un mundo ideal la respuesta hubiera sido: "Perl", pero me informaron que ellos trabajan principalmente en Python, aunque pueden trabajar en otros ambientes, incluyendo Perl.
Después de informarles que en la organización preferimos Perl para el desarrollo de nuestras aplicaciones, y después de un micro debate religioso, uno de ellos (Juan) concluyó:
En definitiva cualquier cosa que se puede hacer en un lenguaje se puede hacer en el otro, solo que en Perl la programación es más arcaica
en aquel momento casi me altero, pero en vista de que llegó el que faltaba y que lo importante era la reunión, me quedé tranquilo.
Ahora en retrospectiva me pregunto: ¿a qué se refería con eso de que Perl es arcaico?, tal vez, Juan se refería a Perl 1 (1987), que era una especie de Shell Script con grep, sed y awk incluido, incluso pudiera pensar que dijera eso hasta de Perl 4 (1991, un poco después de Python 1.0), sin embargo la era actual de Perl es 5 (1994) y en vista de la edad del personaje, creo que en realidad Juan no encontró una palabra que describiera todos los míticos defectos de Perl y terminó usando la palabra incorrecta.
Si Perl fuera arcaico, probablemente la programación orientada a objetos y la programación funcional también lo sean, sin embargo son las dos tecnologías con más impulso en la actualidad, y dado que incluso el sistema de orientación a objetos de Perl fue copiado de Python, asumiré que quiso decir alguna de las siguientes:
  1. Perl es feo
  2. Perl es desordenado
  3. Perl es ilegible
  4. Perl es incomprensible
Voy a responder brevemente a estos prejuicios que se han difundido ampliamente por Internet y para los cuales no hay un sustento real, y mucho menos después del renacimiento de Perl (del que hablaré en otro artículo).

Perl es feo

Como eso es cuestión de gustos, lo que es feo para algunos puede ser muy atractivo para otros. Pero suponiendo que Perl es uno de los lenguajes más feos, tiene características que lo hacen un lenguaje único para resolver una gran cantidad de problemas con facilidad.
Una de las características que hacen la sintaxis de Perl recargada (no necesariamente fea) son los sellos (sigils) que indican el tipo de cada variable, sin embargo esto facilita la extensibilidad del lenguaje y permite la interpolación en las cadenas, y cuando me refiero a que Perl es extensible quiero decir que se puede intervenir en el proceso de compilación para cambiar la sintaxis original, una característica de muy alto nivel que comparte con muy pocos lenguajes, y que es el fundamento de los lenguajes de dominio específico (o DSLs por sus siglas en inglés) que son muy útiles y populares. Perl ofrece al menos tres mecanismos diferentes para lograr este objetivo.
Otra característica es la práctica integración de las expresiones regulares en el lenguaje, logrando el uso extensivo de las mismas, lamentablemente dichas expresiones son feas sin importar el lenguaje. Tomemos como ejemplo el reconocimiento de una instrucción específica de LaTeX:

\begin{document}

En Perl se vería algo como:

if ( $latex =~ m/\\begin\{[a-z]+\}/ ) ...

En Python:

patron = re.compile(r'\\begin\{[a-z]+\}')
if
patron.match(latex):
...

En realidad ninguno de los dos es bonito, pero realmente en Perl es más sucinto y hasta más fácil de entender, y eso que no muestro lo tedioso que es usarlas en Java.
Finalmente está el bendito argumento sobre el estilo y todo aquella tontería de que el compilador obliga a indentar correctamente. Lo digo porque aún cuando es cierto que uno termina acostumbrándose, también es cierto que a veces es un fastidio y estorba, y en esos casos no hay remedio. Mas adelante hay un ejemplo de Python que no es fácil de formatear por lo inflexible de la sintaxis.
Cuando se requiere un estilo de código en particular, se usan herramientas de formateo, por ejemplo para C yo uso indent, y para Perl uso perltidy, últimamente el código que escribo suele estar en el siguiente estilo:

perltidy -l=99 -sbl

No importa como me entreguen el código o como lo transcriba yo mismo, porque lo puedo poner en mi estilo estándar con un solo comando, incluso antes de salvarlo (porque además uso vim).

Perl es desordenado

Los lenguajes no son desordenados, la gente lo es.
Sin embargo hay lenguajes que tienen más mecanismos que otros para organizar un proyecto, en particular Perl ofrece varias formas de organizar el código que se adaptan a muchas necesidades, que van desde la programación en una sola línea (de comandos), hasta la construcción de aplicaciones grandes y complejas.
El lenguaje permite la creación de módulos procedimentales con sus propios espacios de nombres, que incluso se pueden organizar en multiples archivos para ser cargados "on demand", bien enterprise, ¿cierto?.
Perl tiene un sistema básico de orientación a objetos que permite implementar OOP de muchas maneras diferentes, porque mientras muchos lenguajes solo hay una forma fija de manejar objetos, en Perl: "hay más de una forma de hacerlo".
Lo que no hace perl es obligar al programador a seguir una estructura determinada, sin importar si eso se adapta a las necesidades de un programa específico, porque si hiciera eso se parecería a Java.

Perl es ilegible

Esto es solo una combinación particular de los dos mitos que le preceden, aunque también se usa el argumento de que no se puede leer el código que se escribío hace 15 min. y eso bajo ciertas circunstancias es bueno, porque permite escribir programas rápidamente aunque sean sucios, después de todo nadie quiere ponerse a diseñar y documentar siguiendo los principios de ingeniería de software para comprender el último programa que escribí hace unas horas, únicamente porque necesitaba una pista sobre la frecuencia de aparición de los caracteres en una docena de archivos:

perl -MYAML -ne '$c{$_}++for split//;END{print Dump\%c}' datos.txt

Es más fácil escribirlo nuevamente que intentar comprenderlo, claro que el programa es una tontería en Perl porque tiene ciertas construcciones mágicas, pero no intenten algo así con otro lenguaje, porque su mejor escenario es lograr que funciona, pero garantizo que va a ser mucho más largo y difícil.
Sin embargo Perl también permite escribir el código mas bonito si eso fuera lo necesario:

use YAML;
use IO::File;
use strict;

my %cuentas;
my $fd = new IO::File $ARGV[0], "r";
while ( my $line = readline($fd) ) {
    for my $letra ( split( //, $line ) ) {
        $cuentas{$letra}++;
    }
}
print YAML::Dump( \%cuentas )
Estoy seguro de que no es muy difícil para un programador de Python o Ruby comprender lo que hace ese código ¿o sí?, luego la cuestión no es que el lenguaje sea ilegible, sino la motivación para escribir el programa y en todo caso la inteligencia del programador para escribir código legible o fácilmente mantenible aún por compañeros poco experimentados.
Finalmente la menor de mis preocupaciones fue el formato del código, pues la mayoría del trabajo lo hace mi editor automáticamente, pero por si acaso también lo pase por perltidy para que vean lo bien formateado que queda.

Perl es incomprensible

¿Para quién?, por ejemplo, el japonés es incomprensible para mi, pero dudo que lo sea para la mayoría de la gente que vive en Tokio, de igual manera Perl es incomprensible para alguien que no este entrenado para comprenderlo. En la sección anterior di un ejemplo de que se puede trabajar en Perl escribiendo el código de manera tan clara como en Python o en Java.
Ciertamente hay mucho código Perl que es prácticamente incomprensible excepto para los gurúes del lenguaje, pero eso no significa que los programas deban escribirse de esa manera.
Lo que si es cierto es que la forma sucinta que permite ofuscar el código hace de perl, no solo un interpretador sino una herramienta de uso común en la línea de comandos y al mismo tiempo permite la expresión del ingenio y maestría en el dominio del lenguaje. Hay muy pocos lenguajes que permitan hacer los JAPHs como Perl.
La falacia aparece cuando se dicen cosas como: "Perl es maligno porque permite esas cosas", "En Python es imposible hacer programas incomprensibles", ¿ah si?, a ver quien entiende este programita en Python:

for n in range(12):
    exec("abcdefghijkl"[n]+"=lambda x=0,y=0: "+filter(
        lambda x:x not in "\n$\r","""(x*y#x/x!range(x,
y#x+y!b(1,1#d(e~,e~#d(f~,f~#c(e~,e~+d(g~,d(g~,g~))#"%4
d" % a(x,y#map(lambda y:i(x,y),h~#" ".join(j(x)#"\\n".
join(map(k,h~))""".replace("~","()").replace("#",")!")
        ).split("!")[n])
print l()
Y puedo buscar cosas mucho más feas en Java, lo que significa que se pueden escribir programas incomprensibles en cualquier lenguaje, y un novato probablemente lo pueda hacer simplemente aprovechando su ignorancia. Por ello la importancia del personal calificado sin importar cual sea el lenguaje.
Sin embargo no se aprovechará todo el potencial de los programadores a menos que el lenguaje provea mecanismos de abstracción del más alto nivel, y es en este caso que lenguajes como Java y PHP son bastante malos mientras Perl supera a Python y a Ruby con holgura, descalificando completamente cualquier posibilidad de catalogar a Perl como un lenguaje arcaico.
Perl puede ser tan corporativo como cualquier otro lenguaje, y dominar este lenguaje en una organización, supone una inversión donde el código se puede utilizar y reutilizar de muchas maneras: desde los administradores de sistemas hasta los desarrolladores, pasando por los administradores de base de datos, etc.; en soluciones que van desde una simple operación de línea de comandos hasta la programación de una gran aplicación corporativa.

domingo, 23 de agosto de 2009

Salio perl 5.10.1!

A partir de hoy está disponible en el CPAN la nueva versión de perl (5.10.1).
Esta versión acomoda varios bugs que se escaparon en la versión 5.10 e introduce algunas incompatibilidades (muy sutiles, pero incompatibilidades al fin) con 5.10 para mejorar la ortogonalidad del lenguaje, así que asegúrense de leer perl5101delta y especialmente la sección de incompatibilidades.

El Ironman de Perl

La Organización de Perl Iluminado (enlightened perl organisation), está promoviendo un maratón de blogging en perl para de motivar a los miembros de la comunidad a promover el uso de este lenguaje.
Las reglas son simples:
  • Se puede escribir cualquier cosa sobre Perl, desde información básica hasta avanzada incluso chistes (lo que brinda incluso oportunidad a los detractores del lenguaje).
  • Los artículos pueden ser cortos o largos, así que se pueden escribir recomendaciones simples, trucos, etc.
  • Los artículos no tienen que ser necesariamente en inglés, asi que podemos hacer artículos en castellano!
  • Hay que publicar al menos 4 artículos cada 32 dias, y ningún artículo puede tener más de 10 días de separación.
Si estás interesado puedes enviar un mensaje a ironman@shadowcat.co.uk con los detalles del blog, luego inscribe el feed y tu blog será agregado en http://ironman.enlightenedperl.org/.
Yo por mi parte estoy comenzando este blog a ver si logro mantener el ritmo ;-)