sexta-feira, 30 de agosto de 2013

Programação funcional para programadores Java

Em um post anterior comecei uma série sobre Scala, focando em programadores Java. Porém notei que a curva de aprendizado de uma linguagem, principalmente em novo paradigma pode ser drástica demais. Portanto decidi focar mais no aprendizado do paradigma aproveitando o que puder do Java. Apesar de Java não ser funcional podemos aprender algumas técnicas interessantes e melhorar nossos projetos atuais.

Uma característica importante do paradigma funcional é o projeto dos tipos das linguagens. O uso de tipos como listas, mapas, árvores e conjuntos são enfatizados para a manipulação de dados. Nas linguagens mais puras os valores são imutáveis, logo as variáveis devem ser inicializadas com valores válidos. Note que isto é muito importante. Se precisamos inicializar nossas variáveis com valores válidos isso significa que nunca teremos variáveis nulas. O próprio inventor do conceito de nulo reconhece que isso é um erro.

Caso ainda esteja resistente a reconhecer este erro, lembre-se de quantas NullPointerException seu código de produção gerou, ou ao menos quanta complexidade foi adicionada ao código através de condicionais você teve de fazer verificando se algo é diferente de null.

Claro que podemos apelar para o Null Object Pattern, mas nem sempre é trivial e envolve muito trabalho em alguns casos.

O ideal seria o sistema de tipos da nossa linguagem nos ajudar com isso. Vamos ver um exemplo:

public abstract class Option<T> {
public abstract boolean hasValue();
public abstract T get();
public T getOrElse(T alternative) {
return hasValue() == true ? get() : alternative;
}
}
view raw Option.java hosted with ❤ by GitHub
Option é uma classe abstrata que representa um tipo que pode ou não ter valor. Teremos mais duas subclasses, uma indicado a existência de valor e outro a ausência. O método hasValue() informará isso para nós através de sua implementação em cada subclasse. O método get() retornar o valor, caso este exista e por fim getOrElse(T alternative) retorna o valor da Option, caso este seja inexistente é retornada a alternativa passada como parâmetro. As coisas ficarão mais claras com a implementação das subclasses e uso.

Vamos ao subtipo que indica existência de valor:

public final class Some<T> extends Option<T> {
private final T value;
public Some(T value) { this.value = value; }
public boolean hasValue() { return true; }
public T get() { return value; }
@Override
public String toString() { return "Some("+value+")"; }
@Override
public boolean equals(Object other) {
if (other == null || other.getClass() != Some.class)
return false;
Some<?> that = (Some<?>) other;
Object thatValue = that.get();
return value.equals(thatValue);
}
@Override
public int hashCode() { return 37 * value.hashCode(); }
}
view raw Some.java hosted with ❤ by GitHub
Como Some representa a existencia de valor o método hasValue() sempre retorna true, o método get() o valor.

Agora o subtipo que representa ausência de valor:

public final class None<T> extends Option<T> {
public static class NoneHasNoValue extends RuntimeException {}
public None() {}
public boolean hasValue() { return false; }
public T get() { throw new NoneHasNoValue(); }
@Override
public String toString() { return "None"; }
@Override
public boolean equals(Object other) {
return (other == null || other.getClass() != None.class) ? false : true;
}
@Override
public int hashCode() { return -1; }
}
view raw None.java hosted with ❤ by GitHub
Como None representa ausência de valor o método hasValue() sempre retornará falso, e lançaremos uma exceção caso seja tentado recuperar o valor que não existe. Já que None não possui valor, o método equals() sempre retorna true caso seja passada outra instância de None.

Agora vamos fazer uso do nosso novo sistema de tipos:

@Before
public void setup() {
names = new ArrayList<Option<String>>();
names.add(new Some<String>("Renan"));
names.add(new None<String>());
names.add(new Some<String>("Paulo"));
}
@Test
public void usandoGetOrElse() {
String[] expected = { "Renan", "Valor alternativo!", "Paulo"};
System.out.println("*** Usando getOrElse:");
for (int i = 0; i < names.size(); i++) {
Option<String> option = names.get(i);
String value = option.getOrElse("Valor alternativo!");
System.out.println(option + " = " + value);
assertEquals(expected[i], option);
}
}
// Saída:
// Some(Renan) = Renan
// None(Valor alternativo) = Valor alternativo
// Some(Paulo) = Paulo
view raw SomeENone.java hosted with ❤ by GitHub
No código acima criamos 3 Options e iteramos sobre eles chamando o método getOrElse(T alternative), dessa forma, se existe um valor, o mesmo é retornado, caso contrário é retornado o valor alternativo.

Também podemos voltar á abordagem mais clássica, perguntando se existe valor no Option:

@Test
public void verificaSePossuiValor() {
String[] expected = { "Renan", null, "Paulo"};
for (int i = 0; i < names.size(); i++) {
Option<String> option = names.get(i);
if (option.hasValue()) {
String value = option.get();
System.out.println(option + " = " + value);
assertEquals(expected[i], value);
}
}
}
Chamamos o método hasValue para verificar se aquela Option possui um valor e então o usamos.

Por fim, vamos ver como fica o código dos nossos métodos que podem retornar um valor ou ausência de valor e seu uso:

public Option<String> fazAlgo() {
if(se tenho valor retornarei um Some com o valor) {
return new Some<String>(valor);
} else {
// não tenho valor, ao invés de retornar nulo
//e correr o risco do código cliente chamar um método em null, retornarei um None
return new None<String>();
}
}
// Option<String> option = fazAlgo();
Note que o grande ganho com essa abordagem é deixar explicito através de código que o valor que estamos usando é opcional, o retorno do método agora é um Option, bem mais legível e menos passível de erros, pois sendo um Option, devemos determinar se o Option é uma instância de Some antes de usar o valor.

Desenvolvendo nossas próprias classes para melhorar o sistema de tipos nesse caso, parece overhead, uma solução é o uso do Guava, uma lib que possui recursos interessantes que dão um "sabor"funcional para nossos projetos.

Aqui tem uma rápida introdução de como usar esses recursos com o Guava.

Nenhum comentário:

Postar um comentário