Translate

Mostrar mensagens com a etiqueta programação funcional. Mostrar todas as mensagens
Mostrar mensagens com a etiqueta programação funcional. Mostrar todas as mensagens

quinta-feira, 27 de julho de 2023

Lambda Expressions no Java: O Manual Definitivo para um Programador COBOL Padawan Entender o Operador -> sem Medo (e Descobrir que Ele Não é Magia)

 

Bellacosa Mainframe e a lambda expressions no Java

☕ Um Café no Bellacosa Mainframe

Lambda Expressions no Java: O Manual Definitivo para um Programador COBOL Padawan Entender o Operador -> sem Medo (e Descobrir que Ele Não é Magia)

"Todo programador COBOL já passou por isso: você abre um código Java moderno e encontra algo como (x) -> x * 2. Sua primeira reação é pensar: 'Quem apagou metade do programa?' A boa notícia é que ninguém apagou nada. Você está olhando para uma Lambda Expression."


Introdução

Se você trabalha há anos no universo IBM Z, provavelmente sua realidade é composta por tecnologias como:

  • COBOL

  • JCL

  • CICS

  • IMS

  • Db2

  • MQ

  • VSAM

  • z/OS

  • REXX

  • PL/I

Neste mundo, tudo costuma ser muito explícito.

Você declara.

Você identifica.

Você define.

Você documenta.

Você implementa.

Já no mundo Java moderno acontece exatamente o contrário.

Quanto menos código existir, melhor.

É aí que surgem as Lambda Expressions, introduzidas oficialmente no Java 8 (2014).

Para muitos profissionais Mainframe, elas parecem um idioma alienígena.

Mas a verdade é outra.

Você já conhece o conceito.

Só não conhece a sintaxe.


Antes de tudo…

Existe Arrow Function em Java?

Tecnicamente...

Não.

O nome Arrow Function pertence ao JavaScript.

No Java o nome correto é:

Lambda Expression

O símbolo é exatamente o mesmo conceito visual:

->

Por isso muita gente chama informalmente de Arrow Function.


Por que a IBM adicionou Lambdas ao Java?

A resposta é simples:

Performance + produtividade.

Na década de 2000, Java era conhecido por ser extremamente verboso.

Para executar uma simples ação era comum escrever dezenas de linhas.

Exemplo antigo:

Collections.sort(lista, new Comparator<String>() {
    @Override
    public int compare(String a, String b) {
        return a.compareTo(b);
    }
});

Mais de dez linhas.

Hoje:

lista.sort((a,b) -> a.compareTo(b));

Uma linha.

Muito mais legível.


Mas o que é uma Lambda?

A definição acadêmica diz:

Uma função anônima.

Mas para um COBOL Padawan eu prefiro outra definição.

Pense nela como um PARÁGRAFO temporário.

No COBOL fazemos isso:

PERFORM CALCULAR-TOTAL

E em algum lugar existe:

CALCULAR-TOTAL.

    COMPUTE WS-TOTAL =
        WS-VALOR1 +
        WS-VALOR2.

EXIT.

No Java moderno, às vezes não vale a pena criar um método inteiro.

Você simplesmente entrega a lógica diretamente.

Algo parecido com:

(a,b) -> a+b

É como dizer:

"Aqui está o código. Execute quando precisar."


O significado do operador ->

Observe:

(a,b) -> a+b

Lendo em português:

Receba A

Receba B

Execute

Retorne A+B

Visualmente:

ENTRADA
   │
   ▼
(a,b)
   │
   ▼
   ->
   │
   ▼
a+b
   │
   ▼
SAÍDA

O operador -> separa:

lado esquerdo

parâmetros

lado direito

implementação


Comparando com COBOL

COBOL:

ADD A TO B GIVING C

Java:

(a,b) -> a+b

A intenção é praticamente igual.

A diferença é que no Java essa operação pode ser passada como parâmetro.


Uma mudança de paradigma

Durante décadas escrevemos programas assim:

Programa

↓

Métodos

↓

Chamadas

Com Lambdas passamos a escrever:

Programa

↓

Comportamentos

↓

Funções

↓

Funções recebendo funções

Isso é programação funcional.


Um exemplo simples

Sem Lambda:

Runnable tarefa = new Runnable() {

    @Override
    public void run() {

        System.out.println("Executando");

    }

};

Com Lambda:

Runnable tarefa =
    () -> System.out.println("Executando");

Mesmo comportamento.

Muito menos código.


O que mudou?

Antes era necessário criar uma classe anônima.

Agora basta informar:

  • parâmetros

  • comportamento


Quando usar ()

Sem parâmetros:

() -> ...

Um parâmetro:

x -> ...

Dois parâmetros:

(a,b) -> ...

Três parâmetros:

(a,b,c) -> ...

Quando usar chaves?

Uma linha:

x -> x*2

Mais de uma:

x -> {

    System.out.println(x);

    return x*2;

}

O retorno implícito

Observe:

x -> x+1

Não existe:

return

O Java entende automaticamente.

Já quando usamos bloco:

x -> {

    return x+1;

}

O return passa a ser obrigatório.


Interfaces Funcionais

Aqui existe uma diferença enorme para JavaScript.

No Java você não cria uma Lambda do nada.

Ela precisa implementar uma interface.

Exemplo:

@FunctionalInterface

interface Soma {

    int executar(int a,int b);

}

Uso:

Soma s =
    (a,b) -> a+b;

Pronto.

Sua Lambda implementou automaticamente a interface.


O segredo está na @FunctionalInterface

Ela permite apenas:

um único método abstrato.

Isso faz o compilador saber exatamente qual método será implementado.


Onde as Lambdas aparecem no Java moderno?

Praticamente em toda parte.

Principalmente em:

  • Streams

  • Comparator

  • Runnable

  • CompletableFuture

  • ExecutorService

  • Optional

  • Collections

  • APIs REST

  • Spring Boot

  • Jakarta EE

  • IBM Semeru Runtime

  • Open Liberty

Ou seja...

Se você pretende trabalhar com Java no IBM Z...

Vai encontrá-las diariamente.


Streams

Imagine uma lista.

List<Integer> numeros =
List.of(1,2,3,4,5);

Filtrar pares:

numeros.stream()

.filter(n -> n%2==0)

.forEach(System.out::println);

A Lambda diz apenas:

Receba um número

↓

Se for par

↓

Retorne verdadeiro

Comparando com COBOL

No COBOL seria algo como:

PERFORM VARYING IDX
FROM 1 BY 1

UNTIL IDX > MAX

    IF NUMERO(IDX)
       MOD 2 = ZERO

       DISPLAY NUMERO(IDX)

    END-IF

END-PERFORM

No Java:

.filter(n->n%2==0)

O comportamento é exatamente o mesmo.


Comparator

Ordenar nomes.

Antes:

mais de quinze linhas.

Hoje:

lista.sort(

(a,b)->a.compareTo(b)

);

Method Reference

Existe algo ainda menor.

Em vez de:

x -> System.out.println(x)

Podemos escrever:

System.out::println

Isso se chama:

Method Reference.

É praticamente uma Lambda reduzida.


Predicate

Representa perguntas.

Predicate<Integer> par =

n -> n%2==0;

É equivalente a:

É par?

↓

Sim ou Não

Function

Transforma algo.

Entrada:

Nome

Saída:

Maiúsculo
nome -> nome.toUpperCase()

Consumer

Recebe.

Consome.

Não devolve nada.

x -> System.out.println(x)

Supplier

Não recebe nada.

Mas produz.

() -> UUID.randomUUID()

Curiosidade

O nome Lambda vem da matemática.

Mais especificamente do Lambda Calculus, criado por Alonzo Church, em 1936.

Muito antes da invenção do COBOL.

Muito antes do Java.

Muito antes do C.


Alan Turing conhecia Lambdas?

Sim.

Church e Turing trabalharam praticamente na mesma época.

Os dois provaram matematicamente que era possível criar computação universal.


Então Java virou linguagem funcional?

Não.

Java continua sendo:

  • Orientado a Objetos

Mas ganhou recursos funcionais.


O que acontece na memória?

Muita gente imagina que uma Lambda cria um objeto enorme.

Na verdade não.

O compilador gera bytecode especial usando:

invokedynamic

Introduzido na JVM 7.

Depois entra em ação:

LambdaMetafactory

Ela cria dinamicamente uma implementação da interface funcional.

Ou seja...

Não existe necessariamente uma classe física como antigamente.


O papel do invokedynamic

Antes do Java 8:

Classe anônima

↓

Compilador

↓

Novo .class

Depois:

Lambda

↓

invokedynamic

↓

JVM

↓

Objeto criado dinamicamente

Muito mais eficiente.


Performance

Na maioria dos casos:

Lambda é mais rápida que classe anônima.

Por quê?

Porque:

  • menos bytecode

  • menos classes

  • melhor otimização JIT

  • HotSpot entende melhor o fluxo

No IBM Semeru Runtime isso também acontece.


Existe custo?

Sim.

Toda abstração possui custo.

Mas normalmente ele é insignificante.


Captura de variáveis

Exemplo:

int valor = 10;

Function<Integer,Integer> soma =

n -> n + valor;

Funciona.

Agora:

valor++;

Erro.

Por quê?

Porque a variável precisa ser:

effectively final.


Por que isso existe?

Para evitar problemas de concorrência.

Imagine:

100 Threads.

Todas alterando a mesma variável.

Seria um desastre.


O coletor de lixo

As Lambdas normalmente vivem pouco.

São excelentes candidatas para o Young Generation da JVM.

Resultado:

coleta extremamente rápida.


Java no IBM Z

Se você usa:

  • IBM Semeru Runtime

  • OpenJ9

  • Liberty

  • Spring Boot

  • z/OS Connect

Você está usando Lambdas o tempo inteiro.

Mesmo sem perceber.


Limitações

Nem tudo são flores.

Algumas limitações:

  • não possuem estado próprio

  • não substituem classes

  • não substituem objetos complexos

  • podem reduzir legibilidade quando abusadas

  • debugging pode ficar mais difícil

  • stack traces às vezes ficam estranhos


Pontos fortes

✔ Menos código

✔ Mais legibilidade

✔ Melhor integração com Streams

✔ Melhor paralelismo

✔ Melhor manutenção

✔ Melhor otimização da JVM

✔ Código moderno


Pontos fracos

✘ Curva de aprendizado

✘ Sintaxe inicialmente estranha

✘ Pode esconder lógica demais

✘ Debug mais complexo

✘ Não substitui arquitetura


Dicas para quem vem do COBOL

Não tente decorar a sintaxe.

Entenda a ideia.


Sempre pergunte:

"Esse trecho está apenas descrevendo um comportamento?"

Se sim...

Provavelmente cabe uma Lambda.


Streams + Lambdas são inseparáveis.

Aprenda ambos juntos.


Evite Lambdas gigantes.

Se passou de cinco ou seis linhas...

Crie um método.


Prefira Method Reference

Quando possível.

Em vez de:

x -> System.out.println(x)

Faça:

System.out::println

Easter Eggs

1. Nem toda Lambda gera um novo objeto.

A JVM pode reutilizar instâncias.


2. Uma Lambda pode não existir como classe.

Ao contrário das antigas Anonymous Classes.


3. O compilador gera métodos sintéticos.

Eles aparecem como:

lambda$0

lambda$1

lambda$2

Dentro do bytecode.


4. javap revela a mágica

Compile:

javac Exemplo.java

Depois:

javap -c -v Exemplo.class

Você verá instruções como:

  • invokedynamic

  • BootstrapMethods

  • referências ao LambdaMetafactory

É uma ótima forma de entender o que a JVM realmente executa.


5. Lambdas e paralelismo

Elas combinam naturalmente com:

parallelStream()

Mas atenção: paralelo não significa automaticamente mais rápido. Em cargas pequenas, o custo de dividir o trabalho pode superar o ganho.


Fazendo a ponte para o mundo Mainframe

Se você é um Programador COBOL Padawan, pense assim:

  • Um PERFORM executa um bloco identificado pelo nome.

  • Uma Lambda entrega esse bloco diretamente para quem vai executá-lo.

  • Um IF do COBOL pode virar um Predicate.

  • Uma rotina de transformação de registros pode virar uma Function.

  • Um DISPLAY pode ser representado por um Consumer.

O paradigma muda, mas o raciocínio continua familiar: dados entram, uma lógica é aplicada e um resultado é produzido.


Conclusão

As Lambda Expressions não vieram para substituir a Programação Orientada a Objetos, muito menos os princípios sólidos que sustentam aplicações corporativas. Elas surgiram para reduzir código repetitivo, aumentar a expressividade e permitir que o desenvolvedor descreva comportamentos da mesma forma que descreve dados.

Para quem vem do universo IBM Z, elas representam mais uma evolução do que uma revolução. Você já domina lógica estruturada, modularização, processamento em lote, concorrência, desempenho e confiabilidade. O desafio agora é aprender uma nova forma de expressar essas mesmas ideias.

No fim das contas, a Lambda é apenas uma ferramenta. Ela não torna um sistema melhor por si só. Assim como um bom PERFORM, ela precisa ser usada no lugar certo, com clareza e equilíbrio.

E talvez essa seja a maior lição para todo COBOL Padawan que começa a explorar Java no Mainframe: a tecnologia muda, a boa engenharia de software permanece.


segunda-feira, 4 de abril de 2022

☕💣 OPERADOR, O SINAL => ACABOU DE INVADIR O DATA CENTER! Arrow Functions: A Revolução das Funções Modernas Explicada para Quem Vem do COBOL Mainframe

 

Bellacosa Mainframe 

☕💣 OPERADOR, O SINAL => ACABOU DE INVADIR O DATA CENTER!

Arrow Functions: A Revolução das Funções Modernas Explicada para Quem Vem do COBOL Mainframe

Se você programa COBOL há anos, provavelmente está acostumado com estruturas como:

PERFORM CALCULA-IMPOSTO.

ou

CALL "PROGRAMA1" USING WS-DADOS.

Quando entra no mundo JavaScript moderno, uma das primeiras coisas que aparecem é um símbolo aparentemente estranho:

() => {}

Essa construção é chamada de Arrow Function (Função Seta).

À primeira vista parece criptografia de hacker.

Mas, na prática, ela representa apenas uma forma mais moderna e compacta de escrever funções.


Um Pouco de História

As Arrow Functions foram introduzidas em:

ECMAScript 6 (ES6) - 2015

O ECMAScript é a especificação oficial do JavaScript.

A proposta foi desenvolvida por diversos membros do comitê TC39, responsável pela evolução da linguagem JavaScript.

A inspiração veio principalmente de linguagens funcionais como:

  • Haskell

  • Scala

  • CoffeeScript

  • C#

  • Java 8 (Lambdas)

O objetivo era:

  • Escrever menos código

  • Tornar funções mais legíveis

  • Resolver problemas com o contexto do this


Como Era Antes

Função tradicional:

function soma(a, b) {
   return a + b;
}

Uso:

console.log(soma(10,5));

Resultado:

15

A Mesma Coisa com Arrow Function

const soma = (a, b) => {
   return a + b;
};

Resultado:

15

Mesma lógica.

Sintaxe diferente.


Traduzindo para a Cabeça de um Coboleiro

Imagine:

CALCULA-SOMA.
    COMPUTE WS-RESULTADO = WS-A + WS-B.

Arrow Function:

(a,b) => a+b

É como se fosse:

ENTRADA -> PROCESSAMENTO

O símbolo:

=>

significa aproximadamente:

"transforma isso em"

ou

"recebe isto e executa aquilo"

Anatomia da Arrow Function

Exemplo:

const dobro = (numero) => {
   return numero * 2;
};

Partes:

(numero)

Parâmetro de entrada.

=>

Arrow.

{
   return numero * 2;
}

Bloco executado.


Forma Super Compacta

Quando existe apenas um retorno:

const dobro = numero => numero * 2;

Equivale a:

const dobro = function(numero){
   return numero * 2;
}

Comparação Lado a Lado

Tradicional

function quadrado(x){
   return x*x;
}

Arrow

const quadrado = x => x*x;

Exemplo Prático de Mainframe

Imagine um arquivo contendo salários.

COBOL:

PERFORM VARYING I FROM 1 BY 1
   UNTIL I > TOTAL-REGISTROS

   COMPUTE WS-NOVO-SALARIO =
           WS-SALARIO(I) * 1.10

END-PERFORM

JavaScript:

salarios.map(salario => salario * 1.10);

Observe:

salario => salario * 1.10

é uma Arrow Function.


O Que é map()?

O método percorre uma lista.

Para cada elemento executa a função.

Visualmente:

1000
2000
3000

Passa por:

salario => salario * 1.10

Resultado:

1100
2200
3300

Por Que Virou Tão Popular?

Antes:

numeros.forEach(function(numero){
   console.log(numero);
});

Depois:

numeros.forEach(numero => console.log(numero));

Menos código.

Mais legibilidade.


O Grande Problema Que Ela Resolve

No JavaScript existe algo chamado:

this

Ele gera muita confusão.

Exemplo antigo:

function Pessoa() {

   this.nome = "Bellacosa";

   setTimeout(function() {

      console.log(this.nome);

   },1000);

}

Resultado:

undefined

Porque o this muda de contexto.


Com Arrow Function

function Pessoa() {

   this.nome = "Bellacosa";

   setTimeout(() => {

      console.log(this.nome);

   },1000);

}

Resultado:

Bellacosa

A Arrow Function herda o contexto externo.

Isso eliminou milhares de bugs.


Vantagens

1. Menos Código

Antes:

function(x){
   return x*2;
}

Depois:

x => x*2

2. Melhor Leitura

Especialmente em:

filter()
map()
reduce()
forEach()

3. Resolve Problemas de this

Grande vantagem.


4. Ideal para Programação Funcional

Muito usada em:

  • React

  • Angular

  • Vue

  • Node.js


Desvantagens

Não Possui Próprio this

Às vezes isso é ruim.

const pessoa = {
   nome: "João",
   falar: () => {
      console.log(this.nome);
   }
}

Pode não funcionar como esperado.


Não Pode Ser Usada Como Construtor

Isto funciona:

function Pessoa(){}
new Pessoa();

Isto não:

const Pessoa = () => {};
new Pessoa();

Erro.


Menos Clara em Funções Grandes

Isto:

const calcula = () => {
   ...
   ...
   ...
   ...
}

Às vezes fica menos legível que uma função tradicional.


Linguagens que Possuem Conceito Semelhante

Java

x -> x * 2

C#

x => x * 2

Kotlin

{ x -> x * 2 }

Scala

x => x * 2

Python

lambda x: x * 2

Ruby

->(x) { x * 2 }

Como Ler uma Arrow Function

Quando encontrar:

(cliente) => cliente.nome

Leia mentalmente:

Receba cliente
Retorne cliente.nome

Outro exemplo:

(a,b) => a+b

Leia:

Receba A e B
Retorne A+B

Como Construir Uma

Passo 1

Escreva a função tradicional.

function multiplica(a,b){
   return a*b;
}

Passo 2

Remova a palavra function.

(a,b){
   return a*b;
}

Passo 3

Adicione a seta.

(a,b) => {
   return a*b;
}

Passo 4

Se houver apenas um retorno:

(a,b) => a*b

Pronto.


Truque Para Coboleiros

Sempre pense:

ENTRADA => SAÍDA

Exemplos:

x => x*2

"Recebe X e devolve o dobro."


nome => nome.toUpperCase()

"Recebe nome e devolve maiúsculo."


cliente => cliente.saldo

"Recebe cliente e devolve saldo."


Regra de Ouro

Se você enxergar:

=> 

Pergunte:

O que entra?

Está à esquerda.

(cliente)

O que sai?

Está à direita.

cliente.nome

Então:

(cliente) => cliente.nome

significa:

ENTRA CLIENTE
SAI O NOME

Exatamente como uma pequena rotina COBOL que recebe um registro e devolve um campo.


Resumo Bellacosa Mainframe

Se fosse explicar Arrow Function para um operador ou programador COBOL em uma única frase:

Arrow Function é uma forma moderna, compacta e mais inteligente de escrever rotinas pequenas em JavaScript, funcionando como um "PERFORM inline" que recebe dados à esquerda da seta e devolve um resultado à direita.

Quando você começar a estudar React, Node.js, APIs REST, automações em N8N ou agentes de IA em JavaScript, verá Arrow Functions em praticamente todo lugar. Entender => é equivalente a aprender PERFORM, CALL e SECTION nos primeiros dias de COBOL: é um dos fundamentos da linguagem moderna.