Um paralelo entre Programação Declarativa e Programação Imperativa
Tags: programming
Published: 2023-11-16 20:00:41

Quais são os trade-offs entre o Paradigma Imperativo e o Paradigma Declarativo, princípios e uso

---

## Introdução

As linguagens de programação podem ser classificadas de diversas formas, podem ser classificadas por Type System forte ou fraco; pelo nível de abstração (Machine code, Low level Assembler ou High level); se são compiladas ou interpretadas, ou por Paradigmas (Orientação a Objetos, Funcional, Procedural, Lógico, Imperativo, Declarativo entre outros). Neste artigo falaremos sobre o Paradigma Imperativo e Declarativo.

Entender o que são os paradigmas de programação, suas características vai ajudar você a entender melhor outros paradigmas como o Paradigma Funcional, Lógico, ou Orientado a Objetos, pois todos eles tem alguma similaridade com os paradigmas declarativo, declarativo ou ambos.

O Paradigma Imperativo foi o primeiro estilo de codificação que surgiu, com as primeiras linguagens de estudo short code. Já o estilo declarativo começou com a linguagem LISP (1958), com o Prolog (1972), e funcionais como o Haskell (1990).

No lado das linguagens imperativas temos, por exemplo, FORTRAN, ALGOL, Pascal, Ada e C entre outras, já no lado da linguagens declarativas temos, por exemplo: HTML, CSS, YAML e SQL. As linguagens JavaScript, Java, PHP e Rust, por exemplo, são multiparadigma e o estilo depende de como você escreve, mas nenhuma delas é puramente declarativa.

## Paradigma Imperativo

O estilo imperativo concentra-se em como executar o código detalhe a detalhe e define o fluxo de controle de como instruções alteram o estado de um programa. Para ficar mais claro, o código imperativo ordena comandos ao computador, diz exatamente o que precisa fazer, como precisa fazer e em que ordem exata precisa executar.

A exemplo, o código abaixo é um algoritmo de ordenação chamado de bubble Sort que percorre o array `n^2` fazendo a troca dos elementos até ordená-lo completamente. Nesse código há a construção da lógica e fluxo de controle, além da mutabilidade do array.

![bubble sort](https://substackcdn.com/image/fetch/w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0a640f32-3aa6-4420-a048-16e4174e1a81_1348x1350.png)

O código imperativo é interessante para programação de código de baixo nível para o qual existem poucas ou nenhuma abstração. O código fica extremamente verboso, ruim para depurar, ler e para dar manutenção. Em um código imperativo há muitos loops, condicionais e mutações de estado que facilmente resultam em bugs.

Assim sendo, a programação imperativa é mais adequada para tarefas onde é importante controlar o estado do programa e gerenciar o fluxo de execução.

Vamos calcular o fatorial de um número natural positivo, cuja fórmula está na imagem abaixo.

![fatorial](https://substackcdn.com/image/fetch/w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff5c2b102-a99c-45d4-a8ee-71d106821de5_487x157.png)

No fatorial imperativo, avaliamos se `n` é menor ou igual a zero e retornamos 1, do contrário fazemos um loop até `n-1` e multiplicamos o valor de `n` pelo índice fazendo a mutabilidade de `n`, ao final retornamos `n`. Nesse código criamos a lógica de calcular o fatorial de `n` e o fluxo de controle.

![fatorial imperativo](https://substackcdn.com/image/fetch/w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F36ca5586-ae84-49f3-9be8-23b7b7e716d9_1040x630.png)

### Características do código Imperativo
- Execução sequencial;
- Estado mutável;
- Gerenciamento manual de memória;
- Não-determinístico.

### Vantagens
- Domínio explícito sobre o fluxo de controle;
- Eficiência em uso de memória (se utilizado corretamente);

### Desvantagens
- Código verboso;
- Mutabilidade de estado;
- Efeitos colaterais.

### Casos de uso

- Sistemas de baixo nível: programação de sistemas de baixo nível, como drivers de dispositivo, sistemas operacionais e firmware. Essas tarefas requerem manipulação direta de locais de memória e registros de hardware, sendo melhor realizado usando um estilo imperativo.
- Jogos e simulações: jogos e simulações geralmente envolvem algoritmos complexos e atualizações em tempo real, sendo mais fáceis de expressar usando um estilo imperativo. A programação imperativa fornece controle explícito sobre o fluxo do programa, tornando mais fácil escrever mecanismos de jogo eficientes e simulações físicas.
- Operações de E/S: a programação imperativa é adequada para tarefas que envolvem muitos estados mutáveis ​​e efeitos colaterais, como operações de E/S. Programas imperativos podem manipular diretamente locais de memória e registros de dispositivos, tornando-os adequados para essas tarefas.

## Paradigma Declarativo

O Paradigma declarativo, por outro lado, é um modelo de abstração de alto nível. Nesse estilo, declara-se o que deve ser computado ao invés de como deve ser computado, delegamos a implementação de controle para a linguagem.

Assim, a programação declarativa é como escrever um mapa detalhado para chegar a um destino. Você define o objetivo e descreve as etapas necessárias para alcançá-lo, permitindo que o computador determine a melhor rota e execute as ações correspondentes, sem precisar se preocupar com os detalhes de como cada passo é realizado.

Muitas linguagens que aplicam esse estilo tentam eliminar os efeitos colaterais descrevendo o que o programa deve realizar em termos do domínio do problema, em vez de descrever como realizá-lo como uma sequência das primitivas da linguagem de programação.

Conforme **R. Kowalski** em Communications of the ACM, Volume 22, Issue 7, Julho 1979, **um algoritmo é composto pela lógica e pelo fluxo de controle. Na programação declarativa, a linguagem tende a abstrair a fluxo de controle e deixar para o programador desenvolver a lógica**.

![algorithm](https://substackcdn.com/image/fetch/w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F649d082d-40e8-4567-b23f-3779291ec802_452x59.png)

**Lloyd, JW**. no artigo **Practical advantages of declarative programming**, explica que a programação declarativa pode ser entendida como “Forte” e “Fraca”. Isto é, no sentido “Forte”, o programador só fornece a lógica do que precisa e no sentido “Fraco”, o programador precisa fornecer a lógica e estender o fluxo de controle para computar o resultado esperado.

### Princípios da Programação Declarativa

Para alcançar o estilo declarativo de programação, é necessário que alguns conceitos sejam aplicados. Os principais princípios deste paradigma são: _Stateless_, _Transparência referencial_, _Abstração_ e _Ausência de efeitos colaterais_.

![pilares da programação declarativa](https://i.imgur.com/aJz3ZYr.png)

#### Stateless

Em uma linguagem onde todos os dados são imutáveis, aplica-se o conceito de stateless. Isso significa que, uma vez que uma variável é atribuída, seu valor não pode ser alterado. Em vez disso, qualquer manipulação ou transformação de dados envolve o retorno de uma cópia dos valores originais transformados, de forma que os valores originais sem mantenham intactos. Em outras palavras, não fazemos a persistência dos dados entre as chamadas de execuções. Em JavaScript, funções como `map`, `filter` e `flatMap`são stateless e funções como `sort` são stateful.

> Uma função sem estado é aquela que não depende ou manipula o estado de contexto mais amplo. Ele pega tudo o que precisa como parâmetro e retorna um resultado, sem efeitos colaterais e nada armazenado entre as chamadas de execuções.

Conforme **Nick Samoylov** em [**Introduction to Programming**](https://www.oreilly.com/library/view/introduction-to-programming/9781788839129/50f54a6f-dd25-40bc-89d2-31b73d95b6b7.xhtml), as operações sem estado geralmente não representam um problema ao alternar de um fluxo sequencial para um paralelo. Cada elemento é processado de forma independente e o stream pode ser dividido em qualquer número de substreams para processamento independente. Ou seja, o stateless acaba com bugs e race conditions em ambientes multithreading, não há razões para usar locks para sincronizar eventos dependentes.

#### Transparência Referencial

O valor de uma aplicação de função independe do contexto em que ocorre, isto é, dado um input a função deve retornar sempre o mesmo resultado. Por isso, a função deve ser independente do contexto, de forma que não use variáveis ou estados globais.

A função `add` não é referencialmente transparente pois dado o mesmo argumento, ela produz resultados diferentes:

```javascript
let total = 0

const add = a => total += a

const r1  = add(1) // 1
const r2  = add(1) // 2
```

Uma função ou expressão é referencialmente transparente se puder ser substituída por seu valor sem alterar o comportamento do programa.

#### Abstração

A abstração é um dos principais senão o principal princípio do Paradigma Declarativo, é a primeira coisa que vem a mente quando pensamos nela. Como já foi dito, dizemos o que queremos e não como queremos, assim delegamos a responsabilidade do que fazer para a linguagem de programação e apenas descrevemos como queremos o resultado a partir de determinada entrada.

Existem linguagens puramente declarativas, linguagens hibridas e multiparadigmas. YAML, SQL, HTML e XML são exemplos de linguagens puramente declarativas. Linguagens puramente funcionais ou lógicas como Haskell e Prolog, respectivamente, são hibridas pois apesar de ter seus próprios paradigmas (funcional e lógico) enfatizam a programação declarativa. Já as linguagens como Javascript, Java, PHP são multiparadigma, elas incluem algumas abstrações declarativas mas também incluem instruções imperativas e não opinam sobre qual meio usar.

#### Ausência de efeitos colaterais

Efeitos colaterais são alterações de estado ou execução de funções que não estão diretamente ligadas com a produção do resultado esperado de uma função. O Paradigma declarativo tem como objetivo eliminar as mudanças de estado em variáveis globais ou objetos compartilhados. Em vez disso, ela se concentra em avaliar expressões e produzir resultados imutáveis, o que ajuda a evitar problemas relacionados a estado e concorrência.

A função `doSomething` é considerada referencialmente transparente porque sempre retorna o mesmo valor de saída para uma determinada entrada, e não depende de nenhum estado externo. No entanto, apesar de ser referencialmente transparente, a função possui um side effect, ou seja, uma ação colateral que ocorre além da simples computação do valor de retorno, o side effect não faz parte da computação do valor de retorno da função, mas pode ter efeitos observáveis fora do escopo da função.

```js
const doSomething = (arg) => {
  console.log("something");
  return arg;
}
```

### Características do código Declarativo
- Stateless (estado interno não é relembrado entre chamadas);
- Determinístico (as mesmas chamadas produzem os mesmos resultados);
- Paralelismo (execução de múltiplas tarefas simultaneamente);
- Transparência Referencial (uma expressão pode ser substituída por seu valor sem afetar o comportamento);
- Modelo semântico claro e conciso;
- Abstrações de alto nível;
- Independência.

### Vantagens
- Legibilidade;
- Concisão;
- Recuperação de erros;
- Reutilização;
- Comutatividade;
- Sem efeitos colaterais;
- Idempotência.

### Desvantagens
- Baixo nível de controle;
- Programação com estado (Stateful).

### Casos de uso

- Desenvolvimento Web: a programação declarativa é frequentemente usada no desenvolvimento Web, onde HTML, CSS e outras linguagens de marcação são usadas para definir a estrutura e a aparência das páginas da Web. A programação declarativa permite que os desenvolvedores especifiquem a aparência da página, em vez de como obter essa aparência.
- Processamento de dados: a programação declarativa é adequada para tarefas de processamento de dados, como consultar bancos de dados e manipular grandes conjuntos de dados. Linguagens de programação declarativas como SQL fornecem uma interface de alto nível para interagir com dados, tornando mais fácil escrever consultas e transformações complexas.
- Programação funcional: a programação funcional enfatiza o uso de funções puras e estruturas de dados imutáveis. A programação funcional é adequada para tarefas que exigem muita simultaneidade ou paralelismo, pois funções puras não têm efeitos colaterais e podem ser executadas em paralelo com segurança.

## Conclusão

Muitas das linguagens imperativas começaram a trazer novos recursos na última década para o estilo declarativo. Um exemplo é o Java 8 que trouce uma nova API para Streams para operar em coleções usando expressões lambda abstraindo o código imperativo.

No Front-end também ganhamos muito mais produtividade e segurança com os frameworks declarativos que surgiram nos últimos anos, como o React, o Vue, o Angular, o Next, o Nuxt entre outros.

Na área de cloud-computing, também existem várias ferramentas no estilo declarativo como o Terraform, Ansible, Docker, Kubernetes que auxiliam a colocar aplicações em produção com escalabilidade, segurança e confiabilidade, fazendo o CI/CD.

> **A crescente popularidade das linguagens de programação declarativas decorre da simplicidade inerente do paradigma, cujo alto nível de abstração garante estilos de programação legíveis para humanos**. — MOTTOLA A.. **Design and implementation of a declarative programming language in a reactive environment**. 2005.

Se você chegou até aqui, espero que tenha tido uma ótima leitura e que esse conteúdo possa ter lhe ajudado de alguma forma. Obrigado por ler! Qualquer dúvida, agregação ou correção, [envie-me uma dm no twitter ](https://twitter.com/rwietter).

## Referências

- [Declarative Programming](https://en.wikipedia.org/wiki/Declarative_programming#:~:text=Declarative%20programming%20is%20a%20non,by%20a%20declarative%20programming%20style.)
- [Algorithm = logic + control](https://dl.acm.org/doi/10.1145/359131.359136)
- [A Note on Declarative Programming Paradigms and the Future of Definitional Programming](https://www.cse.chalmers.se/~oloft/Papers/wm96.pdf)
- [Practical advantages of declarative programming](https://research-information.bris.ac.uk/en/publications/practical-advantages-of-declarative-programming)
- [CodeDocs - Declarative programming](https://codedocs.org/what-is/declarative-programming)
- [Imperative Programming in Depth](https://blog.ndepend.com/imperative-programming-in-depth/)

Imagem: nordicapis