jueves, 24 de diciembre de 2009

Conociendo al alce (Moose)

En otras ocasiones les he hablado de de Moose, hoy voy a explicar lo más básico de este nuevo sistema de programación orientada a objetos que ha tomado por asalto al CPAN y que muchos desarrollos están adoptando como plataforma oficial de programación orientada a objetos.

Todo este ruido es porque Moose ofece mecanismos únicos de reutilización de código y polimorfismo que además son muy fáciles de utilizar. Así que ahora además de la herencia podrás utilizar roles (también conocidos como mixins), tendrás la capacidad de alterar fácilmente el comportamiento de una clase desde un rol, podrás delegar la de una clase en otra de manerá mágica, establecer contratos, inicialización perezosa, verificación de tipos y docenas de nuevas características que te harán reflexionar sobre como has podido programar toda tu vida sin ellas.

Directo del manual de Moose un ejemplo sencillo de implementación de herencia:

 1 package Point;
 2 use Moose;
 3 
 4 has 'x' => ( is => 'rw', isa => 'Int', default => 0 );
 5 has 'y' => ( is => 'rw', isa => 'Int', default => 0 );
 6 
 7 sub clear {
 8     my $self = shift;
 9     $self->x(0);
10     $self->y(0);
11 }
12 
13 sub distance_sqr {
14     my ($self, $point) = @_;
15     return ($self->x - $point->x)**2 + ($self->y - $point->y)**2;
16 }
17 
18 sub distance {
19     my ($self, $point) = @_;
20     return sqrt $self->distance_sqr( $point );
21 }
22 
23 1;

La línea 2 usa Moose, lo que ejecuta un compilador que transforma el programa, y que de paso activa el modo estricto (use strict) y las advertencias (use warnings).

Luego decimos que nuestro objeto tiene dos atributos (líneas 4 y 5) llamados x y y, estos atributos permiten la escritura y la lectura (is => "rw") y son de enteros (isa => "Int") y tienen un valor por defecto de 0 (default => 0), estas dos líneas generan los métodos de acceso a los atributos que además verifican la validez de los datos que se asignen a dichos atributos, también crean el código necesario para incializar los valores de los atributos en 0.

Luego se implementa una operación que borra el punto, es decir lo pone en (0,0) y otros que calculan la distancia y la distancia al cuadrado de este punto a otro.

Hasta ahora, puede parecer que Moose ahorra trabajo, sin embargo, siguiendo con el ejemplo veamos como sería un punto en 3 dimensiones:

 1 package Point3D;
 2 use Moose;
 3 
 4 extends 'Point';
 5 
 6 has 'z' => ( is => 'rw', isa => 'Int', default => 0 );
 7 
 8 after 'clear' => sub {
 9     my $self = shift;
10     $self->z(0);
11 };
12 
13 around 'distance_sqr' => sub {
14     my ($orig, $self, $point) = @_;
15     return  $self->$orig($point) + ($self->z - $point->z)**2;
16 };
17 
18 1;

Hacer una subclase es realmente simple solo se dice que esta clase extiende a otra (línea 4), se agrega un nuevo atributo y se agregan los métodos. Sin embargo Moose tiene interesantes capacidades que facilitan la reutilización del código, y es aquí donde se le empiza a ver el queso a la tostada.

En vez de escribir un nuevo método clear que sobreescriba el heredado, podemos agregarle un comportamiento al existente, así ni siquiera tenemos que invocar el método de la super clase.

En el caso de distance_sqr envolvemos el código de la super clase con nuestro método, lo que se parece un poco más a la manera tradicional de sobreescribir métodos para luego invocar al código de la super clase utilizando SUPER, sin embargo, estas operaciones le dan una gran potencia a un nuevo mecanismo de polimorfismo: los roles (también conocidos como mixins), del que hablaré en el próximo artículo.

Los objetos creados con Moose se utilizan como cualquier otro objeto, tomando en cuenta que los parámetros del constructor ahora son estilo hash:

1 use Point3D;
2 use Modern::Perl;
3 
4 my $p1 = Point3D->new;
5 my $p2 = Point3D->new( x => 1, y => 1, z => 1 );
6 
7 say "Distance: ", $p1->distance($p2);

Notese que la invocación $p1->distance($p2) invoca el método distance heredado de Point, que a su vez utiliza la envoltura distance_sqr de Point3D, que internamente invoca al método distance_sqr de Point.

lunes, 7 de diciembre de 2009

Rendimiento en PSGI/Plack

[English translation]
En mi articulo sobre PSGI aseveré que Plack era rápido, para demostrarlo comparé la velocidad del programa ejecutándose como CGI en apache (ACGI), como un servidor standalone en CGI::Emulate::PSGI (CEP) y como una aplicación nativa de PSGI.

La prueba no fue muy rigurosa, porque en realidad solo quería confirmar lo que había leído.

El comando para reportar la velocidad fue:

$ ab -n 1000 -c 10 -k "http://localhost:5000/cgi-bin/perldocweb?pod=PSGI&format=source"

Los resultados obtenidos fueron:

ACGI
CEP
PSGI
Peticiones/seg.
10.57
267.17
512.31
Tiempo de ejecución (ms)
94.618
3.743
1.952
Rata de transf. (kBps)
179.52
4539.79
8686.67

Solo por ver la velocidad cruda, me hice un pequeño programa para servir archivos de texto y comparar el rendimiento contra apache sirviendo archivos estáticos:

 1 #!/usr/bin/perl
 2 
 3 use Modern::Perl;
 4 use IO::File;
 5 
 6 my $dir = "/home/jrey/htdocs";
 7 
 8 my $app = sub {
 9     my $env      = shift;
10     my $filename = $dir . $env->{'REQUEST_URI'};
11     return [ '200', ['Content-Type' => "text/plain"], IO::File->new($filename) ];
12 };

Los resultados para el comando:

$ ab -n 1000 -c 10 -k "http://localhost:5000/PSGI.pod"

Fueron:

Plackup
Apache
Peticiones/seg.
614.69
3217.03
Tiempo de ejecución (ms)
1.627
0.311
Rata de transf. (kBps)
10425.21
55133.41

Como dije antes, Plack es muy rápido, y en particular esta prueba muestra que el rendimiento es aceptable incluso para contenido estático, así que podremos desplegar las aplicaciones directamente en perl, sin necesidad de un servidor web adicional, excepto para circunstancias especiales, como alta disponibilidad y balanceo de carga, en cuyo caso también hay algunas soluciones en Perl como perlbal. ¿ya les mencioné existe PSGI para perlbal?

domingo, 6 de diciembre de 2009

Error en CGI::Emulate::PSGI

Mientras trabajaba con el código del articulo anterior, me dí cuenta de que en realidad el ejemplo sobre CGI::Emulate::PSGI no funcionaba correctamente, debido a que no reinicié las variables globales de CGI.

1 use CGI::Emulate::PSGI;
2 use CGI;
3 
4 my $app = CGI::Emulate::PSGI->handler(sub {
5     CGI::initialize_globals();
6     do "perldocweb";
7 })

De otro modo los parámetros del primer request se quedan fijos para siempre.