Mostrando entradas con la etiqueta Perl. Mostrar todas las entradas
Mostrando entradas con la etiqueta Perl. Mostrar todas las entradas

viernes, 22 de abril de 2011

Ya viene Perl 5.14

[English translation by google]

Hace unas horas salió el primer candidato a lo que será Perl 5.14 (ahora tenemos nuevas versiones de perl a cada rato :) en esta versión vienen nuevas características convenientes y algunas optimizaciones (sobre todo en windows).

Simetría entre arreglos, hashes y sus referencias

Esta es una de las características que más me gustan de Perl 5.14, pues simplifica la sintaxis cuando se manipulan referencias.

En el caso de los arreglos las primitivas como push, shift, unshift, pop y slice ahora aceptan referencias permitiendo cambiar el código:

  push @{ $arr->[1] }, 8
  $head = shift @{ $obj->arrayref }

por versiones más sencillas que ya no necesitan la dereferencia:

  push $arr->[1], 8
  $head = shift $obj->arrayref

lo que sin duda se ve mejor, sobre todo para los novatos.

Adicionalmnete keys, values y each trabajan con referecias a hashes, pero también trabajarán con arreglos y sus referecias, en este último caso se asumirán los valores del arreglo y las claves seran los índices enteros, así que se podrán hacer cosas como:

  for ( values $obj->arrayref ) { ... }
  for ( keys $obj->arrayref ) { ... }
  for ( keys %{$hoh->{genres}{artists}} ) {...}

En vez de:

  for ( @{ $obj->arrayref } ) { ... }
  for ( 0 .. @{ $obj->arrayref } ) { ... }
  for ( keys $hoh->{genres}{artists}    ) {...}

simplificando igualmente la sintaxis.
 
Una tabla de la documentación da varios ejemplos adicionales:

  |----------------------------+---------------------------|
  | Sintaxis tradicional       | Sintaxis compacta         |
  |----------------------------+---------------------------|
  | push @$arrayref, @stuff    | push $arrayref, @stuff    |
  | unshift @$arrayref, @stuff | unshift $arrayref, @stuff |
  | pop @$arrayref             | pop $arrayref             |
  | shift @$arrayref           | shift $arrayref           |
  | splice @$arrayref, 0, 2    | splice $arrayref, 0, 2    |
  | keys %$hashref             | keys $hashref             |
  | keys @$arrayref            | keys $arrayref            |
  | values %$hashref           | values $hashref           |
  | values @$arrayref          | values $arrayref          |
  | ($k,$v) = each %$hashref   | ($k,$v) = each $hashref   |
  | ($k,$v) = each @$arrayref  | ($k,$v) = each $arrayref  |
  |----------------------------+---------------------------|

Paquetes con bloques

Generalmente cuando se hace un script rápidamente, es más fácil declarar los paquetes (clases) en el mismo archivo, de igual manera es fácil caer en alguna trampa debido a que se tienen varios paquetes en un mismo entorno léxico, así cuando se declaran pragmas o variables léxicas, estas terminan afectando a otras clases o paquetes porque las declaraciones tienen como alcance léxico a todo el archivo, la manera estándar de evitar esto es declarando cada clase dentro de un bloque que acota su alcance léxico:

  {
    package Uno;
    ...
  }
  {
    package Dos;
    ...
  }
  ...

En perl 5.14 un paquete puede tener un bloque, haciendo que la construcción anterior se vea mejor:

  package Uno {
    ...
  }
  package Dos {
    ...
  }
  ...

Substitución no destructiva

Se implementa utilizando el modificador /r a una substitución, logrando que la variable ligada a la operación no sea afectada por la misma, en su lugar se retornará el valor modificado, así que el siguiente código:

  $result = do {
    my $ret = $text;
    $ret =~ s/pedro/juan/;
    $ret;
  };

Se podrá escribir de manera mas concisa en 5.14:

  $result = $text =~ s/pedro/juan/r

Este modificador también se agregó a la transliteración: tr///r

given devuelve valores

Siendo un fanático de Lisp esta característica me gustó, pues con frecuencia suelo simular (cond ...) en Perl haciendo:

my $result;
given ($val) {
    when(1) { $result = "uno" }
    when(2) { $result = "dos" }
    default { $result = undef }
}

En Perl 5.14 se podrá retornar un valor directamente, encapsulando toda la logica y sin necesidad de utilizar variables auxiliares fuera de la estructura:

my $result = do {
  given ($val) {
    when(1) { "uno" }
    when(2) { "dos" }
    default { undef }
} };

El do todavía es necesario ya que el parser de Perl todavía no reconoce la instrucción given como una expresión, aunque probablemente lo haga en el futuro.

\o{...} para incluir caracteres en código octal

Desde siempre Perl permite especificar caracteres como "\0376" sin embargo esta secuencia solo se permite para caracteres hasta 0777 (511), con la nueva secuencia se pueden especificar caracteres unicode con códigos mucho mayores, además el uso de esta secuencia permite eliminar confusiones con las retro-referencias (backreferences) en las expresiones regulares.

Mejoras en Unicode

Ahora se reconocen todo los nombres del unicode incluyendo abreviaturas y nombres de los caracteres de control, así que se pueden escribir caracteres como: "\N{ACK}", "\N{NBSP}", "\N{BEL}", entre otros. Estos nombres también se reconocen tanto en \N{} como en charnames.

Se introduce el pragma:  use feature "unicode_strings" que permite resolver la mayoría de las inconsistencias en la búsqueda de expresiones regulares con unicode, este pragma permitirá que el resultado funcione igual sin importar si la cadena es utf8 o no.

Se introducen los modificadores /d, /l y /u en las expresiones regulares:
  • /l para compilar la expresión regular como si estuviera en el contexto del pragma: use locale
  • /u para compilar la expresión regular como si estuviera en el contexto del pragma: use feature "unicode_strings"
  • /d permite anular los efectos de los pragmas: use locale y use feature "unicode_strings"
  • /a para interpretar la expresión como si fuera ASCII así \s encontrará exactamente los caracteres [ \f\n\r\t], \d encontrará exactamente [0123456789], \w encontrá exactamente los 63 caracteres [A-Za-z0-9_], y las clases de caracteres [[:posix:]] solamente conseguiran los caracteres apropiados del ASCII, también se afectan apropiadamente: \b y \B. Los complementos de estas clases consiguen cualquier cosa fuera de esto, como se debe.

Filehandle cargará automáticamente IO::File

Ahora cualquier filehandle se comportará como un IO::File, cuando se invoque un método inexistente para un filehandle perl cargará automáticamente IO::File, y reintentará la invocación nuevamente.

Mejoras en las funciones tipo printf

Ahora todas las funciones de la familia printf entienden los modificadores del estándar C90: "hh" (char), "z" (size_t), and "t" (ptrdiff_t), si además perl se compila con un compilador C99 también se interpretará el modificador "j" (intmax_t).

Introspección sobre el proceso de compilación

La nueva variable global ${^GLOBAL_PHASE} permitirá a los programas hacer introspección sobre la fase de compilación que se lleva a cabo, lo que permitirá en el futuro mejorar las capacidades de autoextensión del lenguaje.

Para aquellos que no esten al tanto de las capacidades de Perl, enterense que los programas en Perl pueden ejecutarse en tiempo de compilación para alterar el mismo proceso de compilación del programa, por eso aparecen características como está, que facilitará el desarrollo de nuevas extensiones al lenguaje.

Inyección de operaciones en la máquina virtual

Ahora se pueden registrar nuevas operaciones en la máquina virtual de perl, esto facilitará en el futuro la extensión de la máquina virtual con nuevas propiedades y operaciones.

Otras mejoras

Se optimizó la concatenación de cadenas de caracteres que pueden llegar a ser hasta 100 veces más rápidas en ciertas plataformas (como windows).

Se optimizó el uso de %+ y %- para programas que no los utilizan.

viernes, 8 de octubre de 2010

Moose acelerando

El más reciente y exitoso sistema de OOP para Perl, se esta haciendo cada vez más veloz.

En el blog de Moose podemos apreciar que se trabaja continuamente en perfilar el consumo de recursos y mejorar el rendimiento de este sistema, que en su última versión (1.15) es un poquito más lento compilando, mientras incluye más código "en linea" acelerando la ejecución de los programas.

martes, 5 de octubre de 2010

Aplicaciones no tradicionales en Perl

Acabo de ver una aplicación de esas que tal vez inicialmente no se me hubiera ocurrido hacer en Perl: "The Lacuna Expanse", esta aplicación es un juego y es masivamente paralelo, así que yo hubiera investigado como hacerla en Erlang o Haskell, sin embargo hicieron el servidor 100% en Perl, el cliente genérico esta en Javascript y luego hay otros clientes para telefonos hechos en variedad de lenguajes.

Se explota el uso de JSON RPC, y el CPAN les permitió utilizar Plack, Moose, DBIx::Class y la estrella de la fiesta: JSON::RPC::Dispatcher.

Si quieren más información pueden leer el artículo de Ovid y el de JT Smith.

Aquí les dejo el trailer para que alucinen.

Mi proveedor favorito

Hoy entrevisté a un proveedor que pudiera convertirse en uno de mis favoritos, voy a hacer una interpretación libre de la conversación y a lo mejor se imaginan porque quede gratamente impresionado:

Yo: los llamé porque estoy abrumado con tantas cosas por hacer y necesito alguien que ayude a migrar bases de datos Oracle y aplicaciones de Power Builder.

Proveedor: Nosotros somos especialistas en Oracle y Power Builder entre otros, pero estamos trabajando mucho con PostgreSQL, Perl, Python.

Yo: Y si les pido ayuda para migrar aplicaciones Power Builder a la Web que tecnologías me recomiendan.

Proveedor: Bueno nosotros trabajábamos principalmente con Python usando Django, que es un ambiente excelente, sin embargo nos hemos dado cuenta de que aunque Perl no se ve tan bonito, nos rinde más para nuestro trabajo, así que últimamente estamos trabajando más con Catalyst y Perl en general.

Necesito más proveedores así, porque donde yo trabajo si vamos a migrar a software libre.

domingo, 31 de enero de 2010

Moose de dieta

[English translation]

¿Te parece que Moose es demasiado pesado para tus aplicaciones?

La compilación de los objetos de Moose puede tomar un tiempo considerable durante el arranque de una aplicación, esto podría dar la impresión de que los programas que utilizan Moose son lentos, sin embargo la compilación solamente ocurre al cargar el programa, y dependiendo de la aplicación pude ser que Moose no sea tan pesado como parece.

Un ejemplo de ello es una aplicación en Catalyst (en versiones mayores a la 5.8), cuando arranca se deben compilar todos los objetos hechos en Moose y se siente la diferencia con las versiones anteriores, pero como Catalyst se va a ejecutar durante dias o meses, no importa mucho el tiempo de arranque.

Si la aplicación que quieres desarrollar es un comando que ejecuta una tarea rápida y termina tal vez Moose es muy pesado para tí, sobre todo cuando ejecutas los comandos repetidamente a través de otros comandos como xargs(1) o find(1).

Para los casos donde el tiempo de arranque de la aplicación no se amortiza bien con la ejecución, hay una solución en el CPAN: Mouse.

Mouse es un reemplazo de Moose muy optimizado que permite utilizar la gran mayoría de las características de Moose, pero es mucho más ligero porque esta desarrollado en XS (es decir en C) y se omiten algunas características para aligerar la ejecución.

Según la página del manual de Mouse, Moose solamente falla en el 1% de los tests de  Mouse, haciendo al último muy compatible con el primero, pero la batería de pruebas se ejecuta 400% más rápido y en mi experiencia yo no puedo notar la diferencia entre utilizar Mouse y cosas como Class::Accessor::Fast, y aunque la segunda pudiera ser algo más rápida (algo que no he probado formalmente), tener a disposición un sistema de OOP como Moose definitivamente vale la pena.

Usar Mouse es muy simple, solo hay que cambiar Moose por Mouse, todo lo demás sigue igual.

Las características presentes en Mouse permiten hacer la mayoría de los programas y objetos que necesitas para aplicaciones y herramientas sencillas, y si llegas a necesitar las características adicionales de Moose, como la metaprogramación, Moose es la única opción razonable y no queda otra que pagar el precio de la compilación inicial.

lunes, 18 de enero de 2010

Dulce dulce Moose

[English translation]

Uno de los comentarios que recibí por el artículo anterior fue acerca de como se vería utilizando la sintaxis de MooseX::Declare.

Este módulo provee extensiones sintácticas que van mucho más allá del azúcar sintáctico regular de Moose.  Utilizando la magia negra de Devel::Declare, MooseX::Declare crea toda una nueva sintaxis muy similar a la de Perl6, para declarar las clases y roles de Moose, sin embargo, el uso de estas extensiones me genera sentimientos encontrados.

Por un lado está la apariencia y simplicidad de la sintaxis implementada por el módulo, y por otra parte me he dado cuenta que al cambiar la sintaxis de esta manera dejan de funcionar herramientas que doy por sentadas, como perltidy, que da errores en la declaración de prototipos en los métodos, también dejan de funcionar bien módulos como PPI y hasta el coloreo de vim.

Estoy consciente de que todo es cuestión de reparar perltidy, PPI y el coloreo de vim, pero el detalle es que es difícil implementar cuanta sintaxis se le ocurra a cualquiera que introduzca nuevos módulos en el CPAN.

Siempre he argumentado que una de las ventajas de Devel::Declare es que permite evolucionar Perl5 a través del CPAN, módulos como TryCatch y MooseX::Declare muestran extensiones sintácticas que en algún momento podrían agregarse a Perl5 de resultar ampliamente aceptadas.

Pero ¿cómo llegarán a ser ampliamente aceptadas, si no las usamos porque rompen las herramientas que normalmente usamos?

A lo mejor nada de esto importa porque ya en marzo de 2010 llega Rakudo *, y todo el mundo comenzará la gran migración a Perl6.

Como en realidad tengo la misma cantidad de argumentos a favor y en contra, voy a seguir meditando en cuales tienen mas peso que otros. Pero mientras tanto aqui tienen la versión de roles del juego de los animales en MooseX::Declare, para que uds. se hagan una idea propia sobre sus costos y beneficios:

 1 #!/usr/bin/env perl
 2 use MooseX::Declare;
 3 
 4 class QuestionNode {
 5     has pregunta => ( is => "ro", isa => "Str", required => 1 );
 6     has [ "si", "no" ] => ( is => "rw", isa => "Str|QuestionNode", required => 1 );
 7 }
 8 
 9 role AnimalsGame {
10 
11     has tree => (
12         is      => "ro",
13         isa     => "QuestionNode",
14         default => sub {
15             QuestionNode->new(
16                 { pregunta => 'vive en el agua', no => 'tigre', si => 'tiburón' } );
17         }
18     );
19 
20     method play {
21         my $guess = $self->tree;
22         my ( $node, $branch );
23         while ( ref($guess) ) {
24             $node   = $guess;
25             $branch = $self->si( $node->pregunta ) ? "si" : "no";
26             $guess  = $node->$branch;
27         }
28         return if $self->si("Es un(a) $guess");
29         my $animal = $self->prompt("Nombre del animal: ");
30         my $diff   = $self->prompt(
31             "Una pregunta cierta para $animal pero falsa para $guess: ");
32         $node->$branch( QuestionNode->new( { pregunta => $diff, no => $guess, si => $animal } ) );
33     }
34 
35 }
36 
37 role ConsoleGame {
38     use Term::ReadLine;
39     use IO::Handle;
40 
41     has title => ( is => "ro", isa => "Str", default => "El juego de los animales" );
42     has term => (
43         is         => "ro",
44         isa        => "Object",
45         lazy_build => 1,
46         handles    => { prompt => "readline" }
47     );
48 
49     method _build_term {
50         Term::ReadLine->new( $self->title );
51     }
52 
53     method si(Str $prompt) {
54         while (1) {
55             my $answer = $self->prompt("$prompt? (y/n): ");
56             return ( $2 ? 1 : 0 ) if $answer =~ /^\s*((si|s)|(no|n))\s*/i;
57             $self->term->OUT->print("Responde 's' o 'n'\n");
58         }
59     }
60 
61     method run {
62         $self->play;
63         $self->play while $self->si("Quieres jugar de nuevo");
64     }
65 }
66 
67 class Game with AnimalsGame with ConsoleGame {}
68 
69 Game->new->run;

lunes, 11 de enero de 2010

Evolución del estilo en Perl

[English translation]

Las técnicas y el estilo en la programación es una de esas cosas que van cambiando con el tiempo, como Perl permite extender el lenguaje con facilidad, se forma un círculo evolutivo cuando a su vez las extensiones popularizan nuevos estilos de programación.

Hoy intento dar un rápido vistazo a varias maneras de programar en Perl que he utilizado durante los años, con la esperanza de que podáis apreciar las ventajas del estilo de la programación moderna en Perl.

Todos los programas de este artículo tienen el mismo objetivo, jugar el juego de los animales, que da la ilusión de que la computadora aprende. Sin embargo, no todos los programas utilizan las mismas estructuras de datos o logran el mismo nivel de robustez, en este sentido los estilos "antiguos" son menos robustos que los modernos.

Perl5 Antiguo


En los principios de Perl5 las computadoras tenían menos capacidad, así que los programas solían escribirse de manera bastante compacta, además se usaban "trucos inteligentes", como el uso de los hashes en este programa, en el cual no es fácil comprender como funciona exactamente %tree en el programa:

Estilo 1: Antiguo
 1 #!/usr/bin/env perl
 2 sub prompt {
 3     print $_[0];
 4     $line = <>;
 5     chomp $line;
 6     $line;
 7 }
 8 
 9 sub si {
10     prompt("$_[0]? (s/n): ") =~ /^\s*s/i;
11 }
12 
13 $pregunta = $root = "vive en el agua";
14 %tree = ( $root => [ 'tigre', 'tiburón' ] );
15 do {
16     {
17         $branch   = si($pregunta);
18         $guess    = $tree{$pregunta}[$branch];
19         $pregunta = $guess, redo if $tree{$guess};
20         $pregunta = $root, next if si("Es un(a) $guess");
21         $animal   = prompt("Nombre del animal: ");
22         $diff     = prompt( "Una pregunta cierta para $animal" .
23                             " pero falsa para $guess: " );
24         $tree{$diff} = [ $tree{$pregunta}[$branch], $animal ];
25         $tree{$pregunta}[$branch] = $diff;
26         $pregunta = $root
27     }
28 } while si("Quieres jugar de nuevo");

Son programas como esos lo que le dieron la (mala) fama de lenguaje de solo escritura. Sin embargo por aquellos días se valoraba mucho la inteligencia, y se inventaron los torneos de golf (la comunidad de Perl es la única que conozco que ha jugado golf con el lenguaje). En un partido de golf el programa anterior podría terminar fácilmente como se muestra a continuación:

Estilo 2: Golf
1 #!/usr/bin/env perl
2 sub p{print$_[0];$l=<>;chomp$l;$l}sub a{p("$_[0]? (s/n): ")=~/^\s*s/i}$q=
3 $s="vive en el agua";%t=($s=>["tigre","tiburón"]);do {{$v=a($q);$a=$t{$q}[$v];
4 $q=$a,redo if$t{$a};$q=$s,next if a"Es un(a) $a";$n=p"Nombre del animal: ";
5 $o=p"Una pregunta cierta para $n pero falsa para $a: ";$t{$o}
6 =[$t{$q}[$v],$n];$t{$q}[$v]=$o;$q=$s}}while a"Quieres jugar de nuevo";

Como te imaginarás, programas como el anterior solo lograron empeorar la situación,pues la práctica de este estilo terminó en lugares donde no debió usarse,programas que necesitaban mantenimiento y que por supuesto eran difíciles de mantener. Aun utilizando herramientas como perltidy que reformatean por completo el programa haciendo visible su estructura, es difícil comprenderlo:

Estilo 3: Sucinto
 1 #!/usr/bin/env perl
 2 sub p { print $_[0]; $l = <>; chomp $l; $l }
 3 sub a { p("$_[0]? (s/n): ") =~ /^\s*s/i }
 4 $q = $s = "vive en el agua";
 5 %t = ( $s => [ "tigre", "tiburón" ] );
 6 do {
 7     {
 8         $v = a($q);
 9         $a = $t{$q}[$v];
10         $q = $a, redo if $t{$a};
11         $q = $s, next if a("Es un(a) $a");
12         $n = p("Nombre del animal: ");
13         $o = p("Una pregunta cierta para $n pero falsa para $a: ");
14         $t{$o} = [ $t{$q}[$v], $n ];
15         $t{$q}[$v] = $o;
16         $q = $s
17     }
18 } while a("Quieres jugar de nuevo");

Los nombres en las variables son inútiles, los ciclos son difíciles de seguir y la estructura de datos que se utiliza utiliza trucos muy "inteligentes", que además no funcionan correctamente en algunos casos poco usuales, esto fue típico de alguna época, en las que las soluciones rápidas y sucias fueron mas la norma que la excepción.


Procedimientos y DSL


En este estilo se usan mucho los prototipos convirtiendo cada subrutina en operadores que a veces son difíciles de seguir. Los objetos se utilizan mediante la sintaxis indirecta que trae algunos problemas de ambigüedad.

Estilo 4: Procedimientos y DSL
 1 #!/usr/bin/env perl
 2 use Term::ReadLine;
 3 
 4 use strict;
 5 
 6 my $term = new Term::ReadLine "El juego de los animales";
 7 
 8 sub prompt($) { $term->readline(shift) }
 9 
10 sub si($) {
11     my $prompt = shift;
12     while ( my $answer = prompt "$prompt? (s/n): " ) {
13         return $answer =~ /^\s*((si|s)|(no|n))\s*/i;
14         print { $term->OUT } "Por favor responda 's' o 'n'\n";
15     }
16 }
17 
18 sub play {
19     my $guess = shift;
20     my ($node, $branch);
21     while ( ref $guess ) {
22         $node = $guess;
23         $branch = si $node->{pregunta};
24         $guess = $node->{ramas}[$branch];
25     }
26     return if si "Es un(a) $guess";
27     my $animal = prompt "Nombre del animal: ";
28     my $diff   = prompt "Una pregunta cierta para $animal" .
29                         " pero falsa para $guess: ";
30     $node->{ramas}[$branch] = { pregunta => $diff, ramas => [ $guess, $animal ] };
31 }
32 
33 my $tree = { pregunta => 'vive en el agua', ramas => [ 'tigre', 'tiburón' ] };
34 play $tree;
35 play $tree while si "Quieres jugar de nuevo";

No es que esto este del todo mal, en efecto usar los prototipos permite hacer extender Perl como lo hace Moose, usando el azúcar sintáctico que proveen las subrutinas en Perl.

Una característica interesante de este nuevo programa es una lógica mejor organizada y una estructura de datos mucho más fácil de comprender, sin embargo todavía usa algunas cosas inteligentes, como el acceso a las ramas utilizando el resultado de la comparación en la subrutina si() que retorna 1 en caso de ser cierto, pero undef en caso contrario, claro que Perl convierte undef en "" o en 0 según el contexto en que se use, pero no es una buena práctica andar haciendo eso por todos lados.


Clases hechas a mano


Los objetos en Perl como en cualquier otro lenguaje trajeron las ventajas del encapsulamiento, consistencia y el reuso del código, sin embargo, para obtener todas las ventajas de este tipo de programación había que hacer métodos (subrutinas) que controlaran el acceso a los atributos. Hacer esto en Perl era laborioso, repetitivo y muy muy aburrido:

Estilo 5: Objetos hechos a mano
 1 package QuestionNode;
 2 use Carp;
 3 use strict;
 4 
 5 sub new {
 6     my ( $class, $pregunta, $no, $si ) = @_;
 7     bless { pregunta => $pregunta, no => $no, si => $si }, ref $class || $class;
 8 }
 9 
10 sub pregunta {
11     my $self = shift;
12     return $self->{pregunta} unless @_;
13     croak "pregunta es un atributo de solo lectura";
14 }
15 
16 sub si {
17     my $self = shift;
18     return $self->{si} unless @_;
19     return $self->{si} = shift;
20 }
21 
22 sub no {
23     my $self = shift;
24     return $self->{no} unless @_;
25     return $self->{no} = shift;
26 }
27 
28 1;

 1 #!/usr/bin/env perl
 2 use QuestionNode;
 3 use Term::ReadLine;
 4 use IO::Handle;
 5 
 6 use strict;
 7 
 8 my $term = Term::ReadLine->new("El juego de los animales");
 9 
10 sub prompt($) { $term->readline(shift) }
11 
12 sub si($) {
13     my $prompt = shift;
14     while (1) {
15         my $answer = prompt("$prompt? (y/n): ");
16         return ( $2 ? 1 : 0 ) if $answer =~ /^\s*((si|s)|(no|n))\s*/i;
17         $term->OUT->print("Responde 's' o 'n'\n");
18     }
19 }
20 
21 sub play {
22     my $guess = shift;
23     my ( $node, $branch );
24     while ( ref $guess ) {
25         $node   = $guess;
26         $branch = si $node->pregunta ? "si" : "no";
27         $guess  = $node->$branch;
28     }
29     return if si "Es un(a) $guess";
30     my $animal = prompt "Nombre del animal: ";
31     my $diff   = prompt
32         "Una pregunta cierta para $animal pero falsa para $guess: ";
33     $node->$branch( QuestionNode->new( $diff, $guess, $animal ) );
34 }
35 
36 my $tree = new QuestionNode( 'vive en el agua', 'tigre', 'tiburón' );
37 play $tree;
38 play $tree while si "Quieres jugar de nuevo";
39 

En el ejemplo se siguen usando los prototipos y la sintaxis indirecta para algunas cosas y para otras no.

Hacer los accessors en la clase QuestionNode era claramente una labor repetitiva y rápidamente la comunidad le buscó varias soluciones a este problema, que fueron agregadas al CPAN.


Asistentes de Clases


En el CPAN florecieron muchas herramientas que facilitaban la programación orientada a objetos, desde pragmas como "fields" que verificaban las claves de un hash a tiempo de compilación hasta los objetos invertidos (inside-out) implementados por Class::Std que mejoraban la encapsulación.

Yo fui un fanático de Class::Accessor (en realidad de Class::Accessor::Fast), y si hubiera hecho el programa en aquella época quedaría así:

Estilo 6: Objetos asistidos

game.pl:
1 #!/usr/bin/env perl
2 use AnimalsGame;
3 AnimalsGame->new->run;

QuestionNode.pm:
1 package QuestionNode;
2 use base "Class::Accessor";
3 use strict;
4 
5 __PACKAGE__->mk_ro_accessors("pregunta");
6 __PACKAGE__->mk_accessors("si", "no");
7 
8 1;

AnimalsGame.pm:
 1 package AnimalsGame;
 2 use QuestionNode;
 3 use Term::ReadLine;
 4 use IO::Handle;
 5 use base "Class::Accessor";
 6 use strict;
 7 
 8 __PACKAGE__->mk_ro_accessors(qw(tree term));
 9 
10 sub prompt {
11     my $self = shift;
12     $self->term->readline(shift);
13 }
14 
15 sub si {
16     my $self   = shift;
17     my $prompt = shift;
18     while (1) {
19         my $answer = $self->prompt("$prompt? (y/n): ");
20         return ( $2 ? 1 : 0 ) if $answer =~ /^\s*((si|s)|(no|n))\s*/i;
21         $self->term->OUT->print("Responde 's' o 'n'\n");
22     }
23 }
24 
25 sub play {
26     my $self  = shift;
27     my $guess = $self->tree;
28     my ( $node, $branch );
29     while ( ref $guess ) {
30         $node   = $guess;
31         $branch = $self->si( $node->pregunta ) ? "si" : "no";
32         $guess  = $node->$branch;
33     }
34     return if $self->si("Es un(a) $guess");
35     my $animal = $self->prompt("Nombre del animal: ");
36     my $diff   = $self->prompt(
37         "Una pregunta cierta para $animal pero falsa para $guess: ");
38     $node->$branch( QuestionNode->new(
39         { pregunta => $diff, no => $guess, si => $animal } ) );
40 }
41 
42 sub new {
43     my $class = shift;
44     my $opt   = shift || {};
45     my $title = $opt->{title} || "El juego de los animales";
46     my $term  = $opt->{term}  || Term::ReadLine->new($title);
47     my $tree  = $opt->{tree}  || QuestionNode->new(
48         { pregunta => 'vive en el agua', no => 'tigre', si => 'tiburón' } );
49     return $class->SUPER::new( { tree => $tree, term => $term } );
50 }
51 
52 sub run {
53     my $self = shift;
54     $self->play;
55     $self->play while $self->si("Quieres jugar de nuevo");
56 }
57 
58 1;

Las herramientas de asistencia de OOP captaron la atención del programador y los programas en Perl, se hicieron más fáciles de entender y programar de manera robusta.

Moose


Este sistema es la última palabra en OOP para Perl.

En particular voy a mostrar como sería el programa utilizando herencia múltiple y composición (roles, rasgos, mixins, ...), yo me estoy volviendo un fanático de la última, pues permite implementar objetos como jugar con LEGO evitando algunos problemas comunes de la herencia múltiple. Sin embargo, primero el ejemplo con herencia múltiple.

Estilo 7: Moose con herencia múltiple

game.pl:
 1 #!/usr/bin/env perl
 2 package Game;
 3 use Moose;
 4 
 5 extends qw(AnimalsGame ConsoleGame);
 6 
 7 __PACKAGE__->meta->make_immutable;
 8 no Moose;
 9 
10 Game->new->run;

AnimalsGame.pm:
 1 package AnimalsGame;
 2 use Moose;
 3 use QuestionNode;
 4 
 5 has tree => (
 6     is      => "ro",
 7     isa     => "QuestionNode",
 8     default => sub {
 9         QuestionNode->new(
10             { pregunta => 'vive en el agua', no => 'tigre', si => 'tiburón' } );
11     }
12 );
13 
14 sub play {
15     my $self  = shift;
16     my $guess = $self->tree;
17     my ( $node, $branch );
18     while ( ref($guess) ) {
19         $node   = $guess;
20         $branch = $self->si( $node->pregunta ) ? "si" : "no";
21         $guess  = $node->$branch;
22     }
23     return if $self->si("Es un(a) $guess");
24     my $animal = $self->prompt("Nombre del animal: ");
25     my $diff   = $self->prompt(
26         "Una pregunta cierta para $animal pero falsa para $guess: ");
27     $node->$branch(
28         QuestionNode->new( { pregunta => $diff, no => $guess, si => $animal } ) );
29 }
30 
31 __PACKAGE__->meta->make_immutable;
32 1;

ConsoleGame.pm:
 1 package ConsoleGame;
 2 use Moose;
 3 use Term::ReadLine;
 4 use IO::Handle;
 5 
 6 has title => ( is => "ro", isa => "Str", default => "El juego de los animales" );
 7 has term  => ( is => "ro", isa => "Object", lazy_build => 1,
 8                handles => { prompt => "readline" } );
 9 
10 sub _build_term {
11     my $self = shift;
12     Term::ReadLine->new( $self->title );
13 }
14 
15 sub si {
16     my $self   = shift;
17     my $prompt = shift;
18     while (1) {
19         my $answer = $self->prompt("$prompt? (y/n): ");
20         return ( $2 ? 1 : 0 ) if $answer =~ /^\s*((si|s)|(no|n))\s*/i;
21         $self->term->OUT->print("Responde 's' o 'n'\n");
22     }
23 }
24 
25 sub run {
26     my $self = shift;
27     $self->play;
28     $self->play while $self->si("Quieres jugar de nuevo");
29 }
30 
31 __PACKAGE__->meta->make_immutable;
32 1;

QuestionNode.pm
1 package QuestionNode;
2 use Moose;
3 
4 has pregunta => ( is => "ro", isa => "Str", required => 1 );
5 has [ "si", "no" ] => ( is => "rw", isa => "Str|QuestionNode", required => 1 );
6 
7 __PACKAGE__->meta->make_immutable;
8 1;

Moose es capaz de generar una cantidad de código muy superior a la de herramientas anteriores, que se limitaban a generar las clases y los accessors para los atributos, en Moose se pueden especificar restricciones de tipo, que pueden llegar a ser complejas. Así en QuestionNode "pregunta" es un "Str" (cadena de caracteres), mientras que "si" y "no" pueden ser "Str" o un objeto "QuestionNode", Moose se encarga de implementar todo el código para garantizar ese contrato.

A continuación el ejemplo utilizando composición de objetos, una de las características más resaltantes de este ejemplo es que casi no hay que cambiar nada para utilizar la composición de objetos, lo que habla bastante bien de las capacidades de abstracción de Moose para la reutilización de código.

En este caso la clase Game se arma agregando una clase ConsoleGame (con sus atributos) y una clase AnimalsGame que a su vez usa objetos del tipo QuestionNode.

Estilo 8: Moose con Roles.

en game.pl solo cambia en la línea 5, "extend" por "with":
 
 5 with qw(AnimalsGame ConsoleGame);

en AnimalsGame.pm y ConsoleGame.pm solo se cambia la línea 2 para convertir las clases en roles y se elimina la línea 31 que solo tiene sentido para las clases (solo las clases necesitan hacerse inmutables para tener un mejor rendimiento).

 2 use Moose::Role;
Espero que este artículo te ayude a establecer similitudes y paralelos entre las técnicas que actualmente usas y Moose, que es básicamente el futuro de la programación orientada a objetos en Perl5, pero que además es la manera más fácil de aprender y afianzar conceptos que te serán útiles cuando quieras comenzar a utilizar Perl6.

Otra ventaja (quizás más importante) de programar en Moose, es lograr un estándar de OOP que todos puedan aprender fácilmente, la diversidad de sistemas de OOP, no le hace del todo bien al lenguaje, ya que por alguna razón la gente quiere una sola interfaz, Moose hace esto posible ya que es lo suficiente flexible y potente para implementar cualquier cualquier cosa que se te ocurra.

No esperes más. usa Moose. ¡YA!

lunes, 4 de enero de 2010

Atributos con rasgos en Moose.

[Google translation]
En el articulo anterior vimos lo más básico de Moose, sin embargo una vez que comiences a conocer este sistema de OOP verás que tiene un monton de mecanismos (y conceptos) que facilitan muchas cosas, uno de los más útiles son los rasgos (traits), que permiten aplicar comportamientos a una clase o atributo con mucha facilidad.

Una característica de los rasgos en los atributos es que facilitan el uso de los mismos y permiten operaciones más eficientes.

Veamos un ejemplo, tomando la clase Point del artículo anterior, a la cual le agregaré el método translate, que traslada un punto:

23 sub translate {
24     my ($self, $x_delta, $y_delta) = @_;
25     $self->x( $self->x + $x_delta );
26     $self->y( $self->y + $y_delta );
27 }

Como puedes ver, el manejo de los atributos es fastidioso, aquí es donde los rasgos comienzan a ser útiles, en particular las coordenadas de un punto tienen rasgos numéricos, veamos como mejora el código cuando los aplicamos:

 1 package Point;
 2 use Moose;
 3 
 4 has 'x' => (
 5     traits  => ["Number"],
 6     is      => 'rw',
 7     isa     => 'Int',
 8     default => 0,
 9     handles => { translate_x => "add", }
10 );
11 
12 has 'y' => (
13     traits  => ["Number"],
14     is      => 'rw',
15     isa     => 'Int',
16     default => 0,
17     handles => { translate_y => "add", }
18 );
19 
20 sub clear {
21     my $self = shift;
22     $self->x(0);
23     $self->y(0);
24 }
25 
26 sub distance_sqr {
27     my ( $self, $point ) = @_;
28     return ( $self->x - $point->x )**2 + ( $self->y - $point->y )**2;
29 }
30 
31 sub distance {
32     my ( $self, $point ) = @_;
33     return sqrt $self->distance_sqr($point);
34 }
35 
36 sub translate {
37     my ( $self, $x_delta, $y_delta ) = @_;
38     $self->translate_x( $x_delta );
39     $self->translate_y( $y_delta );
40 }
41 
42 1;

Las líneas 5 y 13 declaran los rasgos (traits) del atributo, es una arreglo porque se pueden agregar tantos rasgos como quieras a un mismo atributo. Las líneas 9 y 17 generan métodos en la clase para representar el comportamiento del rasgo en el objeto, por ejemplo en la línea 9 se define translate_x como el método que implementa la operación add sobre el atributo x.

El rasgo "Number" provee métodos como add, sub, set y otros que puedes consultar en Moose::Meta::Attribute::Native::Trait::Number, que es la clase que los hace disponibles en Moose.

El uso de rasgos no solamente es cómodo, también es más eficiente que utilizar directamente los accesors, en el primer ejemplo se accede al valor de una coordenada, que se suma y se accede nuevamente (para escribirla), en el segundo caso translate_x suma directamente el argumento al atributo.

Estas diferencias se pueden hace más notorias si usamos tipos de datos más complejos, por ejemplo en el caso de las cadenas de caracteres una modificación mediante un accessor puede resultar muy costosa, supongamos un cadena de caracteres:

1 has text => (is => "rw", isa => "Str");

Si queremos afectar la cadena, digamos haciendo una substitución:

1 my $temp = $self->text;
2 $temp =~ s/java/perl/;
3 $self->text($temp);

Esto además de ser incomodo, poco claro y realmente feo (hasta parece Java), puede llegar a ser bastante ineficiente, sobre todo si las cadenas son largas, pues se estan copiando dos veces (cuando se obtienen y cuando se guardan).

En este caso se puede declarar el atributo con un rasgo "String" y usar la operación "replace":

 1 has 'text' => (
 2     traits  => ['String'],
 3     is      => 'rw',
 4     isa     => 'Str',
 5     default => q{},
 6     handles => {
 7         replace_text => 'replace',
 8     },
 9 );

y luego la usas así:

11 $self->replace_text( qr/java/, "perl" );

Cuando se usan arreglos y hashes los rasgos son realmente muy útiles, y lo mejor es que además te puedes hacer tus propios rasgos, aunque esto será para otro artículo.

Por ahora puedes ver los rasgos que ofrece Moose para los tipos de datos nativos de Perl leyendo la documentación de: Moose::Meta::Attribute::Native. Puede que consigas código donde se implementan los rasgos utilizando: MooseX::AttributeHelpers, sin embargo es mucho mejor (y más fácil) usar Moose::Meta::Attribute::Native que ahora es parte de Moose.