2006-09-15

Orientação a objetos em Perl

Uma das linguagens mais poderosas que há é Perl. Criada por Larry Wall em 1987 com base em C, Lisp, AWK e sed, Perl se tornou a base de diversas linguagens modernas, como Python e Ruby.

Possui uma abordagem sintática ligeiramente diferente, resultante de seu lema: «há mais de uma forma de fazer algo». Sua tipagem estupidamente fraca (só três tipos básicos, escalar, vetor e vetor associativo ou hash, mais alguns menos usados, como soquete) ajuda mais do que atrapalha, devido a sua abordagem, o que deixa os defensores da tipagem forte vermelhos de raiva.

Só que a implementação Perl de orientação a objetos é no mínimo excêntrica. Não usei outro adjetivo, como «alienígena», ou «transcendental», porque prefiro reservar estes para a implementação Perl de multithreading (vide as manpages perlthrtut e perlothrtut).

Há uma reformulação de POO para Perl chamada reform, mais parecida com Ruby, mas ainda não consegui fazer funcionar direito.


Em Perl, as classes são chamadas «pacotes», e são declaradas com o operador package (óbvio =P). O final do pacote é onde termina o arquivo ou onde é declarado um novo pacote. Caso o programador queira começar o programa em si no mesmo arquivo onde são criados os pacotes, basta declarar o início do pacote main:

package main;


Mas a forma mais elegante é criar um pacote .pm (por exemplo, exemplo.pm) e importanto o pacote no arquivo .pl (use exemplo;).

O método construtor é chamado new e o destruidor DESTROY (com letras maiúsculas mesmo). Há um procedimento padrão no construtor, que deve ser executado invariavelmente: (1) determinar a classe, (2) criar a instância, (3) transformar a instância numa instância «real» da classe (com o operador bless) e (4) retornar a instância pronta.

Alguns passos podem ser executados simultaneamente.

Vamos a um exemplo: a criação de uma classe Pessoa, com os atributos nome e ano_nasc (ano de nascimento):

package Pessoa;

sub new {
my $this = shift;
my $class = ref($this) || $this;
my $self = {
nome => shift,
ano_nasc => shift
};
return bless $self, $class;
}


Funções em Perl recolhem os parâmetros no vetor especial @_. No caso de métodos, o primeiro argumento é a instância escopo do método, assim como Python.

O operador shift retorna o «próximo» elemento do vetor – não sendo informado qual vetor, o padrão é @_. Se for a primeira execução de shift, retorna o primeiro elemento, se for a segunda, retorna o segundo elemento, e assim por diante.

Assim, $this recebe o primeiro argumento, que é o escopo de execução, ou seja, uma representação da própria classe (diferente de todos os demais métodos =P). O próximo comando refina este dado.

A declaração de $self cria escopo escalar semelhante a um hash, cujas chaves são nome e ano_nasc, e os valores são respectivamente o segundo e o terceiro elementos de @_.

O último comando faz duas coisas: transforma $self em uma instância verdadeira de $class (bless) e retorna a instância (return).

Bem, se você não conseguiu acompanhar até aqui, aconselho voltar lá em cima e começar de novo. =P


Vamos agora criar dois métodos! Um para retornar nome e outro para retornar a idade (calculada a partir de ano_nasc). Primeiro o nome:

sub nome {
my $self = shift;
my $v = shift;
$self->{nome} = $v if ($v);
return $self->{nome};
}


O primeiro comando atribui o escopo atual (a instância) a $self, depois a variável escalar $v recebe o próximo argumento (se não houver, recebe undef, equivalente a null de Java). Se foi passado algum parâmetro, este será atribuído à chave (atributo) nome, depois o valor deste é retornado.

Em suma, $inst->nome retorna nome. =)

Agora vamos à idade:

sub idade {
my $self = shift;
my (
$seg, $min, $hora,
$dia, $mes, $ano,
$dsem, $dano, $isdst
) = localtime(time);
return (1900 + $ano - self->{ano_nasc});
}


Novamente o primeiro comando atribui o escopo atual (a instância) a $self.

O próximo comando parece meio estranho, mas é só olhar melhor: localtime(time) retorna a hora local na forma de um vetor de nove posições, onde as três primeiras são a hora e as três seguintes a data (as três últimas também não são complicadas, e são até bem úteis). Assim conseguimos atribuir o número do ano a $ano.

Só que o número do ano em Perl é dado a partir de 1900! Então 2006 é retornado como 106.

Por último é retornada a diferença entre o ano atual (1900 + $ano) e o ano de nascimento atribuído à instância (ano_nasc).

Herança



Agora a coisa fica melhor!

Os defensores de linguagens orientadas a objetos dizem que Perl não é orientado a objetos e pronto, principalmente os programadores C++ e Java. Estranho isso.

Por que estranho?

Porque Perl além de suportar orientação a objetos, suporta herança múltipla, algo que Java não suporta. =P


Em Perl, a herança (simples ou múltipla) é implementada através do vetor @ISA.

Como exemplo, será criado um pacote Retangulo, com o método desenhar e as chaves (atributos) altura e largura, e será criado um pacote filho Quadrado:

(¡NOVO! Corrigi alguns erros de digitação no código seguinte que poderiam impedir o funcionamento do programa ¡NOVO!)

package Retangulo;

sub new {
my $this = shift;
my $class = ref($this) || $this;
my $self = {
altura => shift,
largura => shift
};
return bless $self, $class;
}

sub desenhar {
my $self = shift;
foreach (1 .. $self->{altura}) {
foreach (1 .. $self->{largura}) {
print '(*)';
}
print "\n";
}
}

package Quadrado;
@ISA = qw/ Retangulo /;

sub new {
my $this = shift;
my $class = ref($this) || $this;
my $lado = shift;
my $self = new Retangulo($lado, $lado);
return bless $self, $class;
}

1


[update 2008-09-09]Fica mais claro fazendo assim:
package Quadrado;
use base qw/ Retangulo /;
[/update]


Não há nada de novo no pacote Retangulo. Talvez o foreach, que repete o loop para cada elemento do vetor informado (o elemento atual é atribuído à variável especial $_). Veja a manpage perlintro.

O pacote interessante é Quadrado. Logo de cara temos a atribuição de @ISA. O operador qw «quebra» a string fornecida (entre / /) em um vetor, onde cada palavra (qw = queue of words, lista de palavras) é um elemento. É aconselhável usar qw sempre que for feita uma atribuição a @ISA.

Assim, Retangulo foi atribuído a @ISA de Quadrado, tornando este filho do primeiro.

O construtor é bastante simples, nada diferente do que foi visto até aqui – obs.: $lado está recebendo o primeiro parâmetro (segundo argumento, o primeiro é o escopo).

A operação mais diferente é a atribuição de $self, que chama o construtor do pacote (classe) pai. Repare que, da forma como está sendo feito, as duas chaves (altura e largura) receberão o valor de $lado (definição de quadrado, lembra?).

Para testar, salve este trecho de código como formas.pm, então crie um arquivo teste.pl:

#!/usr/bin/perl

use formas;

print "Qual o tamanho do lado do quadrado? ";
chomp($_ = <>);

my $sq = new Quadrado($_);
$sq->desenhar;

exit;


Execute então o script:

$ perl teste.pl
Qual o tamanho do lado do quadrado?


Informe um valor (5, ou 12, ou o que você quiser), pressione <Enter> e veja o resultado.


Espero que alguém tenha se empolgado para aprender Perl. =)

[]'s

Referências: