Programação Funcional em Java #1 - Fundamentos básicos do paradigma
Gabriel Suaki
Posted on September 1, 2020
Fundamentos básicos do paradigma + Estrutura de dados
1. Fundamente-se
É comum ouvir palpites em Pull Requests como: "ei, você pode refatorar esse código usando programação funcional. Use filter, map ou reduce". Mas será que programação funcional é sobre isso? Bom, já adianto que não. O que define o paradigma funcional são suas características: Comportamento reproduzível (Referencial transparency), funções puras (The absence of side-effects) e valores imutáveis (Immutability).
1.1. Referencial transparency
Referencial transparency refere-se a substituição de uma função pelo seu valor. No código abaixo, ao substituir a função sum
pelo valor da soma, não há alteração no comportamento da aplicação, independente de quantas vezes é invocada.
final int dez = 10;
final int result = Math.sum(7, 3);
assertEquals(dez, result); // NAIL IT
// outro exemplo com Strings
final String snaked = "Invoice_ID";
final String result = "Invoice ID".replace(" ", "_");
assertEquals(snaked, result); // NAIL IT
Já no código abaixo isso não ocorre. Não podemos prever o valor de result
por ser randômico.
final int cinco = 5;
final int result = new Random().nextInt();
assertEquals(cinco, result); // FAIL IT
1.2. Pure functions
Essa característica é dividida em dois aspectos:
- O valor da função será sempre o mesmo para a mesma entrada. Não importa quantas vezes chamamos
Math.sum(7, 3)
, o resultado sempre será 10. Caso a entrada mude (Math.sum(10, 3)
), o resultado muda também. É importante ressaltar que esse fator descarta qualquer tipo de apontamento externo ou referência mutável. Veja o exemplo abaixo:
private static Integer NINE = 9;
public Integer plusNine(final Integer n) {
return n + NINE;
}
A função plusNine
parece ser pura, tendo seu valor reproduzível. Entretanto, ela aponta para uma variável estática e mutável externa à seu escopo, tornando-a impura.
- Sua execução não possui side-effects. Comportamentos como apontar para uma variável estática, apontar para uma variável fora do escopo da função ou alterar algum estado interno (ou de um parâmetro) são qualificados como side-effects. Dito isto, fica claro que funções
void
possuem side-effects, do contrário, por que invocar uma função que não produz valor ?
1.3. Immutability
Imutabilidade é um conceito bastante conhecido porém pouco trabalhado, principalmente em linguaguem orientada a objetos. Essa característica implica em construir um objeto e não poder alterar o seu estado interno, ou seja, seu valor. No Java as classes mais utilizadas são imutáveis como, por exemplo: String
, LocalDate
, LocalDateTime
, BigInteger
, BigDecimal
.
Exemplo 1. Strings
final String nome = "Gabriel";
final String nomeEmMinusculo = nome.toLowerCase();
assertEquals("Gabriel", nome); // NAIL IT
assertEquals("gabriel", nomeEmMinusculo); // NAIL IT
Exemplo 2. BigInteger
final BigInteger age = BigInteger.valueOf(15L);
final BigInteger agePlusFive = age.add(BigInteger.valueOf(5L));
assertEquals(15, age); // NAIL IT
assertEquals(20, agePlusFive); // NAIL IT
Em ambos exemplos o estado interno do objeto não se alterou, tanto BigInteger.add
quanto String.toLowerCase
produzem o resultado esperado, porém em um novo objeto. Neste próximo exemplo, entretanto, o mesmo não se aplica:
Exemplo 3. ArrayList
final List<Integer> integers = Arrays.asList(1, 2);
final Integer size = integers.size();
integers.add(3);
assertEquals(size, 2); // FAIL IT
O método List.add
produz um boolean
, logo, fica evidente que altera o estado interno da lista para armazenar o novo inteiro, do contrário,List.add
produziria uma nova lista. Podemos dizer então que há side-effect e ArrayList não é imutável.
2. Estrutura de dados
No paradigma Funcional não trabalhamos com classes que encapsulam comportamento e estado. Ao invés, trabalhamos com estrutura de dados as quais são divididas em três tipos: Estruturas mutáveis, estruturas imutáveis e estruturas persistêntes.
2.1. Estruturas mutáveis
É basicamente o que vimos no exemplo 3
. A estrutura ArrayList
do Java é mutável por padrão. Toda operação altera o estado interno da estrutura.
2.2. Estruturas imutáveis
Essas estruturas não se alteram pós construção e, no Java, as operações lançam erros. Veja o exemplo abaixo:
final List<Integer> integers = Collections.unmodifiableList(Arrays.asList(1, 2));
integers.add(3); // throws UnsupportedOperationException
O método unmodifiableList
transforma uma lista mutável em uma nova lista imutável e, feito isso, toda tentativa de alterar o estado interno resulta no erro UnsupportedOperationException
.
2.3. Estruturas persistentes
Nesse tipo de estrutura é possível operar em cima dela mesma. Todavia, o resultado da operação sempre retorna uma nova estrutura baseada na antiga, compartilhando os elementos para otimizar uso da memória. No Java não temos essas estruturas e é ai que entra o Vavr. Na biblioteca Vavr temos a estrutura que representa uma lista ligada conhecida como List.
final List<Integer> count = List.of(1, 2, 3);
Podemos visualizar a lista count como sendo:
O que acontece se manipularmos a lista substituindo o primeiro elemento por 0 ?
final List<Integer> newCount = count.tail().prepend(0);
A operação acima descarta o primeiro elemento "cabeça" newCount
, retornando a "calda" .tail()
, além de adicionar o elemento 0 como primeiro elemento .prepend(0)
. Por ser uma lista persistente, as operações resultam em uma nova lista chamada newCount
, mantendo a anterior count
intacta com mesmo valor (1, 2 e 3).
O truque aqui é gerar uma nova versão da lista primária, reutilizando os mesmos elementos para otimizar o uso de memória.
3. Conclusão sobre FP
Não pense que trabalhar com linguagens funcionais é estar 100% conforme as características. Existem momentos onde não da para escapar dos side-effects. Por exemplo, qualquer funcionalidade que faz I/O (log, stream, req/res, etc.) é um side-effect.
Estamos falando tanto em funções, fiquei curioso, você saberia me dizer qual a diferença entre um método e um função?
Bom, um método não existe por si só, ele precisa de uma classe para existir.
A função, por outro lado, pode viver sozinha, flutuando em um arquivo.
4. Próximos passos
Em breve trarei o segundo post da série: Programação Funcional em Java #2 - Descomplicando Vavr.
5. Referências
Posted on September 1, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.