quarta-feira, 30 de dezembro de 2009

Evolução

Há muito tempo li "What is Life?", um livro de uma famosa e polêmica bióloga chamada Lynn Margulis, mas apenas recentemente percebi alguns paralelos com os computadores. O livro descreve a vida de uma maneira muito diferente da que me foi apresentada na escola; lamento que eu tenha tido professores que se esforçaram para apresentar a biologia da maneira mais insossa possível.

Uma característica muito interessante da vida, segundo Margulis, é que ela evolui para saber cada vez mais a respeito de si própria. Os organismos unicelulares originais não sabiam se estavam no calor ou no frio. Não sabiam também se havia comida por perto. Eles simplesmente executavam o mínimo para sobreviver. Se não estavam no lugar certo, morriam.

Com o tempo, os seres vivos foram adquirindo meios de avaliar o ambiente e a si mesmos. As lesmas procuram a umidade. As baleias conseguem comunicar-se a centenas de quilometros de distância. Os cães conseguem detectar cheiros diluidos a uma parte por milhão. Até as bactérias conseguem saber se estão num ambiente adequado e movimentam-se de acordo.

Os nossos micros, no início, não detectavam nada sobre si mesmos e até a comunicação era difícil. Se um micro de 8 bits estivesse num ambiente quente, não pararia até derreter. Hoje em dia, os PCs têm vários sensores no hardware e publicam por SNMP seus dados vitais. Os micros em rede podem comunicar-se com pares do outro lado do mundo.

O que mais me impressionou no livro é como a vida é uma bagunça. As espécies não são distintas e isoladas de forma organizada. De fato, a vida só pode ser descrita como uma coisa só: a vida na terra. Um elemento não sobrevive sem todos os outros. Nós precisamos das bactérias para nossa digestão; precisamos das plantas para nosso oxigênio; dependemos de vários tipos de seres para nossa alimentação.

As nossas células são compostas por seres que já foram separados. As mitocôndrias geram a energia de nossas células, mas possuem seu próprio DNA, que tem muito em comum com o das bactérias.

Mesmo os primeiros computadores compartilhavam muitas peças. Os mesmos processadores e geradores de vídeo aparecem em centenas de micros; poucos micros de 8 bits não usam o 6502 ou o Z80. Mas hoje em dia avançamos ainda mais. O mesmo hardware pode ser reaproveitado de inúmeras maneiras. Pode-se trocar a placa de vídeo, os discos rígidos, o processador, a placa de som, etc.

As interações entre os seres num determinado momento geram o ambiente para o próximo e, aos poucos, o novo ambiente altera os próprios seres. Houve uma época em que não havia oxigênio. As cianobactérias, as algas e as plantas produzem oxigênio como resultado da fotossíntese. O oxigênio rapidamente junta-se a outros elementos e não haveria a concentração necessária para nossa sobrevivência se ele não fosse constantemente reposto. Então, o comportamento de uma geração de seres criou o ambiente necessário para o aparecimento das próximas gerações.

Os computadores modernos não podem ser fabricados sem o auxílio de computadores. Desenhar um micro de 8 bits com caneta e papel é possível, mas desenhar um processador de 64 bits não. Cada geração forneceu as ferramentas necessárias para a concepção da próxima.

Mas o que falta? A vida, aprendi, é um processo, não um indivíduo ou uma espécie. A vida constantemente se reconstrói e se reconfigura. As células estão constantemente se renovando em nossos corpos; quando uma espécie desaparece, outra logo ocupa seu espaço. A descrição da vida está no DNA e no RNA. No fundo, os corpos dos seres são o substrato para a sobrevivência do código genético. Inicialmente, era possível desenhar um computador sem o auxílio de software de CAD; no momento ele é imprescindível. Imagino que logo o processo todo poderá ser realizado por software, sendo o homem responsável por apenas ditar uma descrição geral do produto final. Em algum momento, será possível ao software desenhar o hardware necessário para executar a próxima geração de software.

segunda-feira, 28 de dezembro de 2009

Selecionando o que não está lá

Um problema interessante em SQL é o de selecionar o que não está na base. Esse problema se manifesta de diversas maneiras e eu vou analisar uma. Os exemplos abaixo usam a sintaxe do Oracle.

Digamos que eu tenha uma tabela de eventos, criativamente denominada EVENTOS, com as seguintes colunas:

  1. DATA DATE
  2. DESC VARCHAR2(200)

Se eu quiser montar um calendário com o número de eventos por dia, posso escrever uma consulta como esta:

select count(1), data
from eventos
group by data

Essa consulta não vai mostrar os dias em que nada acontece. Isto é, ela não seleciona o que não está na base.

Se eu quiser mostrar todos os dias e um zero nos dias sem eventos, tenho que criar dados para poder selecioná-los. Freqüentemente, essa questão é resolvida com código, mas eu acredito que umas poucas linhas a mais em SQL economizam muitas linhas de Java ou qualquer que seja a linguagem da aplicação.

Em primeiro lugar, preciso de uma consulta que produza os dias que me interessam. A consulta abaixo, por exemplo, produz uma linha para cada dia do mês de fevereiro de 2008.

select rownum as dia
from eventos
where rownum<=to_char(last_day(to_date('022008', 'MMYYYY')), 'DD')

Qualquer tabela serve, desde que tenha tantas linhas quantos forem os dias que for preciso representar.

Agora, basta fazer um join com os dados:

select count(data), dia
from (
 select rownum as dia
 from eventos
 where rownum<=to_char(last_day(to_date('022008', 'MMYYYY')), 'DD')
) dias left outer join (
 select data
 from eventos
 where data between to_date('01022008', 'DDMMYYYY')
       and to_date('28022008', 'DDMMYYYY')
) eventos on to_char(eventos.data, 'DD')=dias.dia
group by dia
order by dia

O segundo where não pode estar no select principal, porque excluiria novamente os dias sem dados.

Para selecionar um período maior, é preciso um pouco de aritmética.

select count(data), to_date('11022008')+dia-1
from (
 select rownum as dia
 from eventos
 where rownum<=to_date('17032008', 'DDMMYYYY')
       -to_date('11022008', 'DDMMYYYY')+1
) dias left outer join (
 select data
 from eventos  
 where data between to_date('11022008', 'DDMMYYYY')
       and to_date('17032008', 'DDMMYYYY')
) eventos on eventos.data-to_date('11022008', 'DDMMYYYY')+1=dias.dia
group by dia
order by dia

O primeiro where aumenta o número de dias para a diferença entre as datas do período. Para juntar as tabelas, é preciso subtrair da data do evento o primeiro dia e assim calcular a quantos dias do início do período cada evento está.

A solução para o problema, portanto, está em criar o que se quer selecionar.

O Tarado do Access

Este ano houve muita polêmica sobre a queda da exigência de diploma superior para a carreira de jornalista. Eu acho que exigir um diploma superior é exagero para a maior parte das profissões. Exceto para a Medicina, a Engenharia Civil, a Odontologia e talvez algumas poucas outras, não é realmente necessário passar anos numa universidade.

Eu não excluo a possibilidade de que muitas profissões sejam regulamentadas, mas isso não tem nenhuma relação com o ensino superior. Os jornalistas, por exemplo, poderiam muito bem ter um conselho que regulasse as questões trabalhistas e éticas e ao qual os interessados se associariam livremente (ou através de uma prova, como os advogados).

Eu não acho a polêmica toda tão interessante, exceto por um detalhe. Descobri que as pessoas não consideram a informática uma profissão séria e digna de um diploma. Eu mesmo defendo que o diploma não é essencial, mas se ele for julgado crítico para o jornalismo, não vejo por que não o seria para a informática.

E agora eu apresento um personagem deveras problemático: o tarado do Access. Eu já testemunhei em duas organizações e já ouvi descrito no testemunho de outras pessoas o personagem que vou analisar a seguir.

O tarado do Access geralmente é um contador, mas não necessariamente. Ele produz pequenos aplicativos em Access ou Excel, com boas doses de VB. VB clássico, claro. Esses aplicativos costumam ser realmente úteis e logo são adotados por um grande número de pessoas.

Chega o dia, invariavelmente, em que o aplicativo (ou seus dados) precisa ser integrado à infraestrutura de TI da empresa. Nesse ponto, entra a informática e descobre que nenhuma de suas preocupações diárias foi considerada: modelagem de dados, infraestrutura de servidores, suporte aos usuários, segurança, integração com outros aplicativos, etc. O tarado do Access, é claro, não pode ajudar, porque já está ocupado com a próxima criação e também porque não tem a menor idéia do que seja preciso fazer.

Uma aplicação que eu mesmo vi, tinha em cada tabela uma coluna chamada INUTIL. Era a chave primária e o autor da obra não sabia para que servia, então deu-lhe este lindo nome. Eu acho que qualquer coisa chamada "chave primária" deve ter alguma importância, mas o óbvio é relativo.

Se houvesse a exigência de curso superior (ou mesmo de certificação) para a informática, poderíamos acusar essas pessoas (algumas até bem intencionadas) de execer ilegalmente a profissão. Mas é um pouco pesado para o simples o uso do Access, acho. Afinal, podem surgir situações perfeitamente legítimas, como a do dono de um pequeno estabelecimento que necessite de uma aplicação simples e isolada do mundo.

As organizações sérias não enviam correspondência mimeografada, nem fazem contabilidade em cadernos escolares, mesmo que a lei não os obrigue a isso ou que isso tenha conseqüências sérias. O que as menos sérias não perceberam ainda, é que deixar a informática para amadores coloca em risco seus próprios futuros. E se os dados forem perdidos ou roubados? E se os dados simplesmente não forem confiáveis, porque não foram normalizados?

Em algumas organizações que levam a sério o trabalho da informática, sabem-se quantos watts uma determinada aplicação consome. Ou quanto pesam os servidores. Ou quantos minutos são precisos para recompor o banco de dados, se houver uma falha catastrófica. São casos extremos, mas apontam para a importância da informática na infraestrutura tecnológica de hoje.

Eu creio que, em parte, as pessoas não levam a informática a sério, porque pensam que a compreendem. Elas usam um computador, usam uma planilha e até sabem montar uma apresentação no Powerpoint. Não é tão difícil! E aqueles incompetentes da TI levam meses para escrever uma aplicação que o tarado do Access monta numa semana.

Com tudo isso, continuo achando que não é preciso ter um diploma para a Informática, assim como para a maior parte das profissões. As organizações que permitem que o tarado do Access sobreviva não são organizações sérias e isso provavelmente irá se manifestar em outros aspectos de seu funcionamento. Logo, exigir diploma para a informática não vai salvá-las de sua própria incompetência.

terça-feira, 22 de dezembro de 2009

A máquina do tempo II

Durante a segunda grande guerra, o físico americano Richard P. Feynman trabalhou em Los Alamos em várias tarefas de suporte. Uma delas consistia em realizar algumas contas realmente complicadas. Ele decidiu que calcular manualmente era inviável e acabou optando por usar alguns equipamentos IBM. Naquela época, a IBM fabricava máquinas de cartões que somavam ou multiplicavam ou algumas outras operações.

Para montar um "computador", eram necessárias várias máquinas. A configuração e o uso determinavam o que elas produziriam. Colocavam-se os cartões numa e depois noutra e depois noutra e assim por diante.

Enquanto o equipamento não chegava, Feynman solicitou algumas secretárias e colocou-as sentadas frente a mesas com calculadoras Marchant. Cada uma faria o trabalho de uma máquina IBM.

A simulação foi um êxito. As moças trabalharam à velocidade prevista para as máquinas IBM. É claro que as máquinas IBM não cansariam depois de meia-hora.

Vamos avançar uns 60 anos.

Um PlayStation 3 é capaz de 218 GFLOPS. Digamos que todo o planeta (6 bilhões de pessoas) fosse colocado a fazer contas; supondo que cada pessoa fosse capaz de executar uma conta a cada 5 segundos, teriamos um pouco mais de 1 GFLOP.

segunda-feira, 21 de dezembro de 2009

Programação Incondicional III

As linguagens orientadas a objetos trouxeram à programação uma forma muito eficiente de eliminar ifs: o polimorfismo.

A habilidade de tratar homogeneamente objetos de classes diferentes ajuda a eliminar código como este:

if(shape instanceof Circle) {
 drawCircle();
} else if(shape instanceof Square) {
 drawSquare();
}

A solução, evidentemente, é criar uma superclasse e colocar nela um método que as subclasses possam sobrescrever, sem que os clientes dessas classes tenham que se preocupar com qual método estão chamando. No seguinte exemplo, eu crio uma instância de Circle, mas uso uma referência a Shape (sua superclasse). Eu invoco draw() (que deve estar definido em Shape) e o método que vai ser invocado é o que está definido em Circle.

Shape s=new Circle();
s.draw();

Se eu tiver um array de Shape, posso invocar draw() em cada elemento, sem considerar qual método estou realmente invocando.

for(int i=0; i<shapes.length; i++) {
 shapes[i].draw();
}


No entanto, ainda há espaço para eliminar mais ifs! Uma característica da maior parte das linguagens orientadas a objetos é que o método invocado depende do tipo da referência usada, não do tipo do objeto. O seguinte exemplo ilustra bem o que quero dizer. Eu não tento invocar diretamente draw() em cada Shape, mas tento fazê-lo através de outro método draw() da própria classe Shape; eu poderia querer contar os tipos das instâncias ou tomar alguma outra ação.

import static java.lang.System.out;

public class Shape {

 private static int circles, squares;

 public void draw() {
  //
 }

 public static void draw(Shape s) {
  out.println("I drew a shape");
 }

 public static void draw(Circle s) {
  out.println("I drew a circle");
  circles++
 }

 public static void draw(Square s) {
  out.println("I drew a square");
  squares++;
 }

 public static void main(String... args) {
  Shape[] shapes=new Shape[] { new Circle(), new Square() };

  for(int i=0; i<shapes.length; i++) {
   draw(shapes[i]);
  }
 }

}

class Circle extends Shape {

 public void draw() {
  //
 }

}

class Square extends Shape {

 public void draw() {
  //
 }

}


No método main(), eu percorro um array do tipo Shape[] e invoco um método draw() para cada referência. Na classe Shape, há três métodos draw(); cada um recebe um tipo diferente de Shape. No entanto, como eu estou usando referências a Shape no laço, somente o método draw(Shape) é invocado.

Em algumas situações eu posso realmente desejar tomar uma ação diferente, conforme o tipo do objeto, não da referência. Nesse caso, eu estaria de volta a usar ifs e o operador instanceof.

Algumas poucas linguagens usam um tipo de invocação chamado double-dispatch. Nele, o tipo da referência não importa, mas sim o tipo do objeto. Em Java ou C#, não existe esse mecanismo, mas é possível emulá-lo com o padrão Visitor.

Então, vamos reescrever o exemplo acima. Vou criar um PaintVisitor e adicionar um método a Shape.

public class Shape {

 public void accept(PaintVisitor v) {
  visit(this);
 }

}

class Circle extends Shape {

 public void accept(PaintVisitor v) {
  v.visit(this);
 }

}

class Square extends Shape {

 public void accept(PaintVisitor v) {
  v.visit(this);
 }

}

class PaintVisitor {
 public void visit(Shape s) {
  out.println("I drew a shape");
 }

 public void visit(Circle c) {
  out.println("I drew a circle");
 }

 public void visit(Square s) {
  out.println("I drew a square");
 }

 public void draw(Shape s) {
  s.accept(this);
 }

}

Finalmente, eu posso reescrever aquele laço da seguinte maneira:

PaintVisitor visitor=new PaintVisitor();
for(int i=0; i<shapes.length; i++) {
 visitor.draw(shapes[i]);
}

E sem um if sequer!

Em algumas situações, o padrão Visitor pode ser realmente útil. Ao construir um editor de textos, eu descobri que era muito melhor separar o código de desenho dos objetos, porque era difícil dar o contexto necessário para cada objeto desenhar a si próprio. Um parágrafo, por exemplo, pode conter imagens e textos de diferentes tamanhos. Fazer cada elemento calcular sua posição e depois desenhar a si próprio, recursivamente, torna o código deveras complicado e extenso. A solução de criar um Visitor para calcular as posições e outro para desenhar o texto tornou o código muito menor e mais simples.

Quando for preciso percorrer uma estrutura de dados, vale a pena considerar o padrão Visitor.

sexta-feira, 18 de dezembro de 2009

Programação Incondicional II

Em 1987, a Acorn lançou um micro de 32 bits impressionante: o Archimedes. Era o micro mais rápido e rodava a 4 MIPS. O processador tinha sido desenvolvido pela própria Acorn e chama-se ARM (Acorn RISC Machine). Hoje em dia, os processadores ARM estão por todas as partes, porque são usados em celulares. Como a Acorn não existe mais, a sigla passou a representar Advanced RISC Machine.

A primeira aparição do ARM foi como um segundo processador para o BBC. Como o BBC tinha um barramento (The Tube) de 1MHz especial para conectar outros processadores, havia uma oferta bem interessante de opções: Z80, 8086, 32016, etc. O mais comum, era ligar um Z80 para rodar CP/M ou um segundo 6502 a 4Mhz para rodar programas com mais velocidade e com mais memória (como o processador principal ficava encarregado do I/O, o secundário deixava até 44KB de memória livre para programas em BASIC).

Pois bem, com o contexto histórico estabelecido, vamos aos detalhes técnicos relevantes. O ARM é uma arquitetura RISC que faz uso de pipelines para melhorar o desempenho. E num processador com pipeline (acho que hoje em dia, todos têm), as instruções mais danosas são as de branch.

Para evitar quebrar o pipeline, os projetistas do ARM decidiram reaproveitar a lógica de comparação em todas as instruções. Em assembly de 6502, para decidir quando fazer um pulo, faz-se o seguinte:

CMP #FF ;Compara o acumulador A com &FF
BEQ .next ;Pula para .next se igual (Branch if EQual)

No ARM, cada instrução pode ser complementada com um teste.

Vamos comparar dois trechos de código que produzem o mesmo resultado. Em primeiro lugar, código com pulos tradicionais. Este é um algoritmo para encontrar o maior denominador comum:

gcd cmp r0, r1 ;Terminou?
beq stop
blt less ;se r0 > r1
sub r0, r0, r1 ;subtrai r1 de r0
bal gcd
less sub r1, r1, r0 ;subtrai r0 de r1
bal gcd
stop

Usando as instruções condicionais, o código fica muito mais compacto.

gcd cmp r0, r1 ;se r0 > r1
subgt r0, r0, r1 ;subtrai r1 de r0
sublt r1, r1, r0 ;ou subtrai r0 de r1
bne gcd ;terminou?

No segundo trecho, as instruções de subtração são complementadas com gt e lt. Conforme o resultado da instrução cmp, será executada a primeira ou a segunda subtração. Uma instrução que não é executada consome um ciclo, mas não quebra o pipeline. Além disso, o código fica muito mais compacto.

Estão prometidos há algum tempo netbooks com processadores ARM. Mal posso esperar!

quinta-feira, 17 de dezembro de 2009

Programação Incondicional

Não faltam metodologias, plataformas e tecnologias, mas resolvi criar mais uma filosofia de programação mesmo assim. A programação incondicional (ifless programming ou unconditional programming) não é um novo radicalismo. É apenas uma tentativa de reduzir bugs através do seu maior vetor: o if.

Todas as linguagens de programação possuem ifs (exceto Prolog). Parecem inevitáveis, mas, na maior parte dos casos, é possível eliminar muitos ifs sem dificuldades. Vou mostrar como.

Uma técnica muito simples (e talvez a mais antiga encarnação da programação incondicional) é o array de ponteiros para funções. É tão simples, que pode ser usada em Assembly, como um array de endereços.

Em C, posso declarar um array de funções que recebem dois ints e retornam int assim:

int (*p[4])(int x, int y);

Posso invocar qualquer uma das funções assim:

resultado=(*p[op]) (i, j);

E com isso evito a seguinte seqüência particulamente perniciosa de ifs:

if(op==0) {
resultado=somar(i, j);
} else if(op==1) {
resultado=subtrair(i, j);
} else if(op==2) {
resultado=dividir(i, j);
} else ...

Perl oferece uma das sintaxes mais simples para declarar estruturas de dados; posso declarar o array e as funções de uma só vez de maneira bastante compacta.

my @ops = (
sub { $_[0]+$_[1] },
sub { $_[0]-$_[1] },
sub { $_[0]/$_[1] },
sub { $_[0]*$_[1] },
);
my $resultado=$ops[2]->(3,2);

Java, infelizmente, não possui funções, mas elas podem ser simuladas com subclasses. Como no Perl, é possível criar as subclasses e o array ao mesmo tempo, mas a sintaxe é muito mais complicada.

Operador[] ops=new Operador[] {
new Operador() {
public int eval(int i, int j) {
return i+j;
}
},
new Operador() {
public int eval(int i, int j) {
return i-j;
}
},
//etc, etc, etc...
};
int resultado=ops[2].eval(3,2);

Usar um hash (array associativo) é só um caso especial dessa técnica. Nenhuma linguagem permite um exemplo tão compacto como Perl:

my %ops = (
'+' => sub { $_[0]+$_[1] },
'-' => sub { $_[0]-$_[1] },
'/' => sub { $_[0]/$_[1] },
'*' => sub { $_[0]*$_[1] },
);
my $resultado=$ops{'+'}->(3,2);

E esta foi a introdução à programação incondicional!