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.

3 comentarios:

  1. Me parece una buena introducción, de las que te dejan con ganas de más, que
    espero vengan pronto.

    Más arriba dices que la directiva `use Moose` ejecuta un compilador que
    transforma el programa, ¿ es un filro de código fuente entonces ? ¿ No
    presenta los mismos problemas al intentar depurar paso a paso el programa ?

    ResponderEliminar
  2. Una de las cosas buenas que tiene Moose es que no está basado en filtros, en realidad fue un error decir que transforma el programa, en realidad el programa se construye a partir de código en Perl que contiene funciones como has() que agregan azúcar sintáctico.

    La depuración no es tan fácil como en Perl plano, porque Moose genera mucho código y como todo compilador los nombres no son para nada bonitos, si haces un paso a paso te darás cuenta de la cantidad de clausuras y vueltas que da el código antes de llegar a tu líneas de código, que por cierto no son alteradas de ninguna forma, así que siempre puedes ir hasta una línea o método particular con el debugger.

    Este es parte del precio que pagas por las nuevas capacidades que se ofrecen, la otra parte es por supuesto el consumo de recursos.

    ResponderEliminar
  3. Ah, pues mucho mejor entonces, con el tiempo he llegado a "odiar" los filtros
    de código porque por mucho que ofrezcan en un principio, al final se
    convierten en algo casi imposible de depurar.

    Y respecto al código añadido y el consumo de recursos estoy de acuerdo en que
    es un precio a pagar por lo que se obtiene; gracias a tu entrada le he echado
    un vistazo al manual (incluso he comprado la versión digital del libro) y
    estoy empezando a interesarme por el sistema. Falta por ver qué tal se integra
    (si es que lo hace) con algún sistema persistente tipo DBI ó similar.

    ResponderEliminar