segunda-feira, 14 de dezembro de 2009

Perlismos em Java

Linguagens dinâmicas como Perl costumam ter algumas pequenas facilidades que reduzem significativamente a quantidade de código necessária para executar operações com strings, listas e hashes. É difícil ou impossível recriá-las em linguagens mais rígidas, como Java ou C.

Mesmo assim, acho que vale a pena investigar o que pode ser reaproveitado. Vou começar com grep e map, que são funções que processam listas. Grep é usado para selecionar itens de uma lista e map para mapear uma lista para outra.

Em Perl, grep recebe dois parâmetros: um bloco de código (ou uma expressão) e uma lista. Para cada elemento da lista, grep executa o bloco ou avalia a expressão. Se o resultado for verdadeiro, o elemento é adicionado à lista de saída. O exemplo abaixo é do manual do ActivePerl:

@foo=grep(!/^#/,@bar);# weed out comments

Este código mostra uma chamada a grep com uma expressão regular (seleciona linhas que não começam com #) e uma lista @bar.

Java, infelizmente, não tem funções propriamente ditas, então vai ser preciso escrever grep como um método estático:

import java.util.*;

public class Grep {

  public static <T> List<T> grep(List<T> in, Grepping<T> mapping) {
    List<T> out=new ArrayList<T>();
    for(T t : in) {
      if(mapping.map(t)) {
        out.add(t);
      }
    }
    return out;
  }
}

O código usa generics, então parece mais complicado do que é. Em poucas palavras, ela recebe uma lista de elementos do tipo T e um mapeamento para elementos desse mesmo tipo; ela retorna uma lista do mesmo tipo T.

A classe Grepping é bastante simples e já fornece um comportamento default (sempre retorna true):

public class Grepping<T> {

  public boolean map(T t) {
    return true;
  }
}

Suponha que seja preciso selecionar um grupo de pessoas, conforme a idade. Precisamos de uma classe para pessoas e outra para lista de pessoas.

public class Pessoa {

  private String nome;
  private int idade;
  private char sexo;

  public Pessoa(String nome, int idade, char sexo) {
    this.nome=nome;
    this.idade=idade;
    this.sexo;
  }

  public String nome() {
    return nome;
  }

  public int idade() {
    return idade;
  }

  public char getSexo() {
    return sexo;
  }

}

public class PessoaList extends ArrayList<Pessoa> {

}

Com isso, posso escrever um mapeamento por idade.

public class PessoaPorIdade extends Grepping<Pessoa> {

  private int limiar;

  public PessoaPorIdade(int limiar) {
    this.limiar=limiar;
  }

  public boolean map(Pessoa p) {
    return p.getIdade()>=limiar;
  }

}

Então, seu eu quiser selecionar todos os maiores de 18 anos, posso fazer o seguinte:

  maiores=Grep.grep(pessoas, new PessoasPorIdade(18));

Nas situações em que o mapeamento não precisa ser parametrizado, gosto de criar um membro estático assim:

public class PessoaGrepping {

  public static final Grepping<Pessoa> MULHERES=
    new Grepping<Pessoa>() {
      public boolean map(Pessoa p) {
        return p.getSexo()='F';
      }
    }

  public static final Grepping<Pessoa> HOMENS=
    new Grepping<Pessoa>() {
      public boolean map(Pessoa p) {
        return p.getSexo()='M';
      }
    }
}

Usar os mapeamentos é simples:

  mulheres=Grep.grep(pessoas, PessoaGrepping.MULHERES);

Para simular a função map, basta criar uma classe Mapping, que retorne uma instância de T no lugar de um booleano:

import java.util.*;

public class Map{

  public static <T> List<T> map(List<T> in, Mapping<T> mapping) {
    List<T> out=new ArrayList<T>();
    for(T t : in) {
      T r=mapping.map(t);
      if(r!=null) {
        out.add(r);
      }
    }
    return out;
  }
}

public class Mapping<T> {

  public T map(T t) {
    return t;
  }
}

O resultado não é tão simples como o que se pode escrever em Perl, mas já é um avanço. Quando Java tiver closures, certamente será possível escrever código ainda mais compacto para executar essas funções.

Nenhum comentário: