DEV Community 👩‍💻👨‍💻

Cover image for Programação Funcional em Java #1 - Fundamentos básicos do paradigma
Gabriel Suaki
Gabriel Suaki

Posted on • Updated on

Programação Funcional em Java #1 - Fundamentos básicos do paradigma

Programação Funcional em Java

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

1.2. Pure functions

Essa característica é dividida em dois aspectos:

  1. 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;
}
Enter fullscreen mode Exit fullscreen mode

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.

  1. 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

Podemos visualizar a lista count como sendo:

List(1, 2, 3)

O que acontece se manipularmos a lista substituindo o primeiro elemento por 0 ?

final List<Integer> newCount = count.tail().prepend(0);
Enter fullscreen mode Exit fullscreen mode

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).

List(0, 2, 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

Oldest comments (0)

Thank you.

Thanks for visiting DEV, we’ve worked really hard to cultivate this great community and would love to have you join us. If you’d like to create an account, you can sign up here.