terça-feira, 24 de setembro de 2019

Java em gotas: Lambda 101


#java #functionalprogramming #lambda

Continuando como nosso tutorial de Stream API e Lambda, vamos ver os fundamentos de programação funcional em Java, com dois exemplos bem simples: https://www.mycompiler.io/view/8vFJbvQ e
https://www.mycompiler.io/view/4wL0H3B




My Compiler


Ambos estão hospedados no My Compiler, que é um ambiente interativo com várias linguagens de programação, no qual você pode desenvolver e testar algoritmos gratuitamente.

Programação funcional

A Wikipedia tem a melhor definição do que seria "programação funcional":

Em ciência da computação, programação funcional é um paradigma de programação que trata a computação como uma avaliação de funções matemáticas e que evita estados ou dados mutáveis. Ela enfatiza a aplicação de funções, em contraste da programação imperativa, que enfatiza mudanças no estado do programa. Enfatizando as expressões invés de comandos, as expressões são utilizados para calculo de valores com dados imutáveis.
Um dos grandes problemas da programação convencional, ou "imperativa", é que, a cada comando, temos que mudar o estado do programa, através das variáveis mutáveis. Isto gera código mais difícil de entender e, com o tempo, pode provocar o efeito de "britleness".

Python suporta programação funcional com vários recursos, como: Generators, List Comprehension e Lambdas :

line_list = ['  line 1\n', 'line 2  \n', ...]

# Generator expression -- returns iterator
stripped_iter = (line.strip() for line in line_list)

# List comprehension -- returns list
stripped_list = [line.strip() for line in line_list]

# Lambda functions
adder = lambda x, y: x+y

(fonte: https://docs.python.org/3/howto/functional.html)

Mas Java carecia de suporte à programação funcional, até a versão 1.8.

Interfaces funcionais

Para entender o conceito de lambda em Java, é preciso começar pelas interfaces funcionais. Uma interface funcional é uma interface com um único método abstrato. Veja o exemplo: https://www.mycompiler.io/view/8vFJbvQ

interface Bhaskara {
 public double delta(double a, double b, double c);
}

O que deduzimos desta interface? Sua assinatura: Ordem e tipos dos parâmetros e, embora não faça exatamente parte da assinatura, o tipo de valor de retorno. Com isso, podemos montar uma expressão funcional e atribuir seu valor à esta interface, dando-lhe uma implementação instantânea e mutável:

Bhaskara bas = (a,b,c) -> Math.pow(b,2)-4*a*c;

Tudo o que está do lado direito do sinal de igual é uma expressão lambda, ou uma função. As letras antes da seta ("->") são os argumentos, e o texto depois da seta é a implementação e também a expressão cujo resultado será o retorno da função.  Note que não precisamos declarar o tipo de dados dos argumentos e nem do valor de retorno, pois isto foi inferido a partir do tipo de dados da variável "bas".

E isso serve para quê? Bem, é uma variável! Pode ser guardada, serializada e até passada como argumento para outros métodos ou funções. Pode até ser invocada diretamente:

System.out.println(bas.delta(2.0,8.0,-24.0));

O retorno e o corpo da expressão lambda

No exemplo anterior, nossa expressão lambda é bem simples e retorna o resultado do cálculo. Nem precisamos usar o comando "return". Mas, se nossa expressão for complexa, com mais de um comando, precisaremos separá-los com ponto-e-vírgula e também teremos que utilizar o "return".

Veja o exemplo: https://www.mycompiler.io/view/4wL0H3B

Declaramos outra interface funcional e uma variável que recebe outra expressão lambda:

interface Raiz {
    public double [] calcular(double a, double b, double c);
}
...
Bhaskara bas = (a,b,c) -> Math.pow(b,2)-4*a*c;
Raiz raiz = (a,b,c) -> {double x1=0.0; double x2=0.0; double d=bas.delta(a,b,c); if (d>0) {x1=(-b+Math.sqrt(d))/(2*a); x2=(-b-Math.sqrt(d))/(2*a);}; return new double []{x1,x2};};

A expressão associada à variável "raiz" é uma expressão lambda complexa, portanto, tive que cercá-la com chaves "{" e "}" e colocar ponto-e-vírgula nos comandos. Ela usa a expressão da variável "bas". Como é uma expressão complexa, tive que declarar um comando "return" explícito.

Na verdade, seria melhor que ela fosse declarada como um método mesmo, mas eu quis mostrar a você uma expressão complexa.

Mas, qual é a vantagem?

Em primeiro lugar, como são funções, é possível comprovar formalmente o funcionamento do programa, através de uma prova matemática. Em segundo lugar, temos a modularidade, seguida pela facilidade de composição e, finalmente, de depuração e teste.

Cada variável é um valor imutável e retorna alguma coisa quando invocada. Elas não mantém estado interno, portanto, o programa fica mais desacoplado.

Continuarei a postar coisas sobre: Lambda, Stream API e outros em Java. Aguarde.

Cleuton Sampaio, M.Sc. e Arquiteto certificado Java.









Nenhum comentário:

Postar um comentário