Logo Passei Direto
Buscar
Material
páginas com resultados encontrados.
páginas com resultados encontrados.

Prévia do material em texto

e-Book 2
MSC Rafael De Moura Moreira
PARADIGMAS DE 
LINGUAGENS DE 
PROGRAMAÇÃO
Sumário
INTRODUÇÃO ������������������������������������������������� 3
PARADIGMAS DE PROGRAMAÇÃO ��������������� 4
PROGRAMAÇÃO IMPERATIVA ���������������������� 6
PROGRAMAÇÃO ORIENTADA A OBJETO ���� 11
PROGRAMAÇÃO FUNCIONAL����������������������19
PROGRAMAÇÃO LÓGICA �����������������������������26
PROGRAMAÇÃO ORIENTADA A EVENTOS �� 28
PROGRAMAÇÃO CONCORRENTE ����������������31
CONSIDERAÇÕES FINAIS ����������������������������38
REFERÊNCIAS BIBLIOGRÁFICAS & 
CONSULTADAS ��������������������������������������������42
3
INTRODUÇÃO
Olá, estudante! Neste material iremos conhecer um 
pouco sobre alguns paradigmas de programação 
– maneiras diferentes de pensar e organizar um 
programa. 
Conheceremos os conceitos que definem os pa-
radigmas mais conhecidos e algumas linguagens 
de programação que os representam bem. Além 
disso, estudaremos também alguns conceitos 
importantes que nos ajudarão a compreender os 
diferentes paradigmas, como subprogramas, clas-
ses, objetos, eventos e conceitos de programação 
concorrente.
Vamos lá? Bons estudos!
44
PARADIGMAS DE 
PROGRAMAÇÃO
Paradigmas de programação são formas diferentes 
de se pensar um programa. Diferentes paradigmas 
possuem diferentes objetivos, e podem alterar ra-
dicalmente a forma de se programar. Eles podem 
focar em formas diferentes de se modelar dados 
ou de passar instruções para o computador.
Para adotarmos um paradigma específico, é ne-
cessário que a linguagem ofereça os recursos 
apropriados para que os conceitos do paradigma 
escolhido possam ser devidamente implementados. 
Na prática, várias linguagens surgiram com foco 
na adoção ou suporte de um paradigma especí-
fico, e por conta disso é comum que as próprias 
linguagens sejam classificadas em função de seu 
paradigma. 
Porém, frequentemente é possível utilizar uma 
mesma linguagem para programar em mais de 
um paradigma diferente. Da mesma maneira, é 
comum que conceitos relacionados a mais de um 
paradigma coexistam em um mesmo programa.
Existe uma enorme diversidade de paradigmas, e 
estudiosos chegam a debater se certas caracte-
rísticas de algumas linguagens constituem para-
digmas próprios ou se elas seriam apenas casos 
55
particulares de outros paradigmas. Estudaremos 
aqui os seguintes paradigmas:
 y Programação imperativa;
 y Programação orientada a objetos;
 y Programação funcional;
 y Programação lógica;
 y Programação orientada a eventos;
 y Programação concorrente.
Diferentes estudiosos podem divergir um pouco na 
forma de classificar estes paradigmas. É comum, 
por exemplo, considerar que a programação orien-
tada a objetos seja um caso bastante particular 
de programação imperativa – Sebesta (2018), 
por exemplo, considera que a orientação a objeto 
é apenas um conjunto de recursos adicionais 
que algumas linguagens imperativas passaram 
a oferecer. 
Não nos aprofundaremos aqui em discussões 
acadêmicas e seguiremos a interpretação mais 
comum. Mas é importante saber que existem outros 
paradigmas e outras abordagens para estudá-los.
6
PROGRAMAÇÃO 
IMPERATIVA
O paradigma imperativo é o mais natural do ponto 
de vista do hardware dos sistemas computacionais 
modernos. Seu nome deriva do modo imperativo, 
a forma verbal que caracteriza ordens ou instru-
ções. Na programação imperativa, o programa é 
constituído por uma sequência de comandos que 
o computador deverá executar.
É intuitivo imaginar esse modo na prática. O próprio 
hardware foi projetado de maneira imperativa: a 
linguagem binária nativa das máquinas consiste 
em um conjunto de operações elementares, e os 
programas são sequências específicas dessas 
instruções.
Chega a ser difícil imaginar um paradigma que não 
seja imperativo. Mas é comum que sejam feitas 
comparações entre o paradigma imperativo (e 
seus paradigmas derivativos) com os paradigmas 
considerados declarativos, como o funcional e o 
lógico (que serão explicados logo abaixo), em que 
o programador foca em explicar o que o programa 
deve fazer, e não como fazer.
Não é incomum encontrarmos os nomes “impe-
rativo” e “procedural” sendo usados de maneira 
intercambiável, mas podemos considerar o pa-
7
radigma procedural como um “caso especial” da 
programação imperativa.
Programação procedural
A programação procedural é uma forma de pro-
gramação imperativa: o programador organiza 
sequências de comandos ou instruções que o 
computador deverá executar.
O que caracteriza a programação procedural é a 
forma de organizar as instruções: o programa não 
será formado por um único bloco longo e contínuo 
de comandos. Ao invés disso, as instruções podem 
ser agrupadas em módulos, ou subprogramas. Um 
programa passa a ser uma coleção de subprogra-
mas, cujo cada subprograma é uma sequência de 
comandos seguindo o paradigma imperativo.
Subprograma
Subprogramas podem ter diferentes nomes em 
diferentes linguagens, e em alguns casos, os nomes 
diferentes podem representar diferenças sutis entre 
elas. Esses nomes incluem sub-rotinas, procedimen-
tos e funções. Em algumas linguagens, a palavra 
“procedimento” diz respeito a um subprograma 
qualquer, uma sequência de instruções. Já uma 
função seria um “caso especial” de procedimento 
que retorna um valor. Outras linguagens não fazem 
essa diferenciação. Neste material, deste ponto 
8
em diante, utilizaremos “função” como sinônimo 
de subprograma.
Linguagens de programação costumam oferecer 
uma sintaxe própria ou mesmo uma palavra-chave 
específica para definir uma função. Normalmente, 
junto dessa definição, devemos dar um nome para 
a função, informar uma lista de parâmetros, e 
dependendo da linguagem, o tipo de seu retorno.
Quando o nome de uma função “A” qualquer aparece 
durante a execução de outra função “B”, dizemos 
que a função “A” está sendo chamada. Quando 
uma função é chamada, a execução da função que 
a chamou é interrompida momentaneamente e o 
fluxo de execução passa para a função chamada. 
Esta será executada até o final, e em seguida a 
execução da função anterior é retomada do ponto 
que parou.
Dizemos que funções respeitam o escopo dos 
dados do programa. Isso significa que dados inter-
nos de uma função não podem ser acessados ou 
modificados por outras funções, garantindo uma 
melhor modularização do programa.
Como os dados de uma função são privados, para 
que dados sejam passados de uma função para 
a outra, existe o conceito de parâmetros de uma 
função. Parâmetros são “dados de entrada” para 
uma função. Quando a função é chamada, quem 
9
a chamou pode passar um conjunto de dados 
para ela.
Essa passagem pode ser uma mera cópia dos 
valores – a chamada passagem por valor, ou 
passagem por cópia, na qual a função que foi 
chamada receberá apenas uma cópia dos dados 
e não poderá modificar os dados originais – ou 
uma referência para a região de memória contendo 
os dados originais – conhecida como passagem 
por referência, que permite que a função altere 
os dados originais.
Após o final de sua execução, uma função também 
pode fornecer dados para a função que a chamou. 
Chamamos esses dados de saída de uma função 
de retorno da função. O retorno é comum em fun-
ções que possuem uma resposta – por exemplo, 
uma função que resolva um problema matemático, 
como o cálculo de uma raiz quadrada – ou em 
funções que devem informar um status, como se 
suas instruções foram executadas com sucesso 
ou se ocorreu algum erro.
Como o programa escrito em uma linguagem pro-
cedural é constituído por uma coleção de funções, 
é importante que o computador saiba qual delas é a 
primeira função que ele deverá executar. É comum 
que as diferentes linguagens tenham o conceito 
de uma função principal, frequentemente chamada 
10
de main. Ela é considerada o ponto de entrada do 
programa. Ela será responsável por inicializações 
em geral e por fazer as primeiras chamadas para 
outras funções. Após todas as execuções das ou-
tras funções, o fluxo sempre retornarápara essa 
função principal antes de encerrar o programa.
Diversas linguagens de programação estruturadas 
surgidas entre o final dos anos 50 e os anos 70 
são linguagens procedurais, como ALGOL, COBOL, 
BASIC, FORTRAN e C. Várias outras linguagens que 
suportam outros paradigmas, como o orientado a 
objeto, também suportam a programação proce-
dural, como o C++, o C# e o Python.
1111
PROGRAMAÇÃO 
ORIENTADA A OBJETO
Em muitas literaturas, a programação orientada a 
objeto é considerada um caso particular ou uma 
extensão da programação imperativa. Isso ocorre 
porque ela também possui estratégias de modula-
rização do código onde, internamente, as unidades 
serão sequências de comandos ou instruções para 
o computador.
Porém, a modularização adotada pela progra-
mação orientada a objeto é bastante diferente 
da programação procedural e envolve diversos 
conceitos novos.
Esse paradigma pode ser utilizado para modelar 
no programa entidades do mundo real. As entida-
des serão representadas por objetos. Os objetos 
possuem suas próprias informações internas, 
bem como suas próprias ações e habilidades, e o 
programa resulta da comunicação entre diferentes 
objetos. Vamos observar alguns conceitos básicos.
Classes e objetos
Cada entidade envolvida no modelo será represen-
tada por um objeto. Um objeto possui diversas 
informações particulares, para uso interno. Cada 
uma das informações internas de um objeto é cha-
1212
mada de atributo. O conjunto de informações de 
um objeto em um dado momento é o seu estado.
Além de informações, objetos também possuem 
suas próprias ações, que são representadas por 
métodos. Um método é uma forma de subprograma, 
e possui muitas características em comum com 
as funções da maioria das linguagens procedurais. 
Dentro dos métodos, trabalhamos de maneira 
imperativa, listando instruções que deverão ser 
seguidas pelo computador.
A grande diferença entre uma função convencional 
e um método é conceitual: a função tipicamente é 
chamada por outra função e pode receber dados 
como parâmetros para utilizar em sua computação. 
O método é chamado pelo objeto, e apesar de poder 
receber parâmetros, consideramos que ela age 
primariamente sobre o próprio objeto, acessando 
seus atributos e podendo alterar seu estado.
É possível que vários objetos possuam caracte-
rísticas em comum. Por exemplo, ao projetarmos 
um sistema acadêmico onde cada aluno possa 
consultar suas disciplinas, trabalhos pendentes, 
notas e faltas, cada aluno seria um objeto diferente. 
Porém, sabemos que eles terão características em 
comum: todo aluno terá um login, uma senha, um 
nome, um documento de identidade, um número 
de matrícula, uma lista de disciplinas etc. 
1313
Podemos criar uma classe para representar alu-
nos. A classe será a ideia abstrata de aluno, e diz 
quais informações (atributos) e comportamentos 
(métodos) cada objeto aluno possuirá. Um objeto 
é uma instância de uma classe e representa uma 
entidade real, concreta.
Princípios da programação orientada a 
objeto
Quando bem utilizada, a programação orientada a 
objeto oferece uma modularização bastante pode-
rosa. Classes podem ser desenvolvidas de maneira 
independente por diferentes programadores e con-
seguirão interagir no programa sem dificuldades. 
Além disso, é fácil expandir as funcionalidades de 
uma classe ou corrigir erros com impacto mínimo 
no restante do programa.
Para que todos esses benefícios possam ser des-
frutados adequadamente, é importante que alguns 
princípios básicos sejam respeitados: encapsula-
mento, abstração, herança e polimorfismo.
O princípio do encapsulamento dita que cada ob-
jeto deve ser responsável por seu próprio estado. 
Isso significa que os atributos de um certo objeto 
não devem sofrer interferência de outros objetos. 
Idealmente, apenas o próprio objeto pode alterar 
seus atributos. Para que isso seja atingido, atributos 
1414
devem ser privados, podendo ser alterados apenas 
dentro dos métodos daquele objeto.
O próximo princípio, abstração, tem bastante 
relação com o princípio do encapsulamento. Ele 
dita que um objeto deve “esconder” toda sua 
complexidade e fornecer uma interface simples 
para interagir com o restante do programa. Isso 
significa que quando planejamos uma classe, ela 
deve ser a mais autossuficiente possível, e os 
métodos daquela classe devem ser capazes de 
resolver todos os problemas relacionados a obje-
tos daquela classe. Esses métodos, por sua vez, 
devem ser fáceis e intuitivos de serem chamados 
por outros objetos. A comunicação entre objetos 
(seja de uma mesma classe ou não) ocorre através 
dos métodos: um método de um objeto recebe 
outro objeto e chama os métodos desse objeto 
passando diferentes parâmetros.
O mais intuitivo dos princípios provavelmente é a 
herança. Ela é bastante similar à herança que co-
nhecemos na biologia. Quando dizemos que uma 
classe é herdeira de outra classe, a “subclasse” 
automaticamente possui os mesmos atributos e 
métodos da “superclasse”, sem que precisemos 
explicitamente reimplementar todo o código 
correspondente. Esse princípio é extremamente 
poderoso, permitindo uma modularização muito 
grande e evitando a repetição desnecessária de 
1515
código, facilitando e agilizando não apenas o de-
senvolvimento, mas também testes, atualizações 
e correções. 
O último princípio é o polimorfismo. Essa palavra 
vem da combinação das palavras de origem grega 
poli e morphos, que significam, respectivamente, 
“várias” e “formas”. O princípio do polimorfismo 
dita que um mesmo objeto pode ser interpretado 
como pertencente a diferentes classes.
O polimorfismo pode se manifestar de diferentes 
maneiras, dependendo dos recursos disponíveis na 
linguagem. A forma mais básica está relacionada à 
herança: se uma classe “Aluno” é herdeira de uma 
classe “Pessoa”, objetos da classe Aluno podem 
ser reconhecidos também como objetos da classe 
“Pessoa”, e funções projetadas para trabalhar com 
Pessoa também conseguem lidar com Aluno sem 
a necessidade de ajustes.
Podemos observar o polimorfismo se manifestando 
de outras formas. Na linguagem Java, por exemplo, 
existe um recurso chamado de “interface”. Uma 
interface não é uma classe, mas uma espécie de 
“contrato”. A interface prevê alguns métodos que 
deverão ser implementados pelas classes que 
aderirem a esse “contrato”. Objetos de qualquer 
classe que implemente a interface podem ser tra-
tados simplesmente como objetos dessa interface.
1616
Outro exemplo mais extremo é o chamado “duck 
typing” do Python. Esse princípio é representado por 
um lema em inglês, que traduzido para o português 
fica “Se ele faz ‘quá-quá’ como um pato e anda como 
um pato, então ele é um pato”. Isso significa que 
a maioria das funções e classes em Python não 
se importa com qual a classe original de um certo 
objeto. Se ele possui os métodos esperados, sem 
problemas. Isso permite a elaboração de códigos 
extremamente genéricos e reutilizáveis.
Linguagens orientadas a objeto
Após sua explosão de popularidade ainda nos anos 
80, as linguagens orientadas a objeto se tornaram 
o padrão de fato para projetos grandes e comple-
xos, e até hoje elas são extremamente populares.
Na prática, muitas linguagens orientadas a objeto 
são consideradas multiparadigma, podendo ser 
utilizadas também de maneira imperativa pura, de 
maneira procedural ou combinando paradigmas 
em um mesmo programa, por exemplo, ao utilizar 
classes e objetos simultaneamente com concei-
tos de outros paradigmas, como o tratamento de 
eventos ou funções lambda.
Após o surgimento das primeiras linguagens orien-
tadas a objeto, como Simula e Smalltalk nos anos 
70, surgiram evoluções de outras linguagens já 
existentes incorporando o suporte à programação 
1717
orientada a objeto, que é o caso do C++ (baseado 
no C), Delphi (baseado no Pascal) e VisualBasic 
(baseado no BASIC).
Algumas dessas linguagens seguem evoluindo 
e apresentando novos recursos até hoje, como o 
C++ e o VisualBasic (atualmente parteda família 
.NET da Microsoft). 
Outras linguagens também surgiram, aperfeiçoando 
ideias trazidas por essas primeiras linguagens ou 
focando em diferentes aspectos da programação. 
Um exemplo bastante clássico é o Java, que utiliza 
a ideia de uma máquina virtual permitindo que 
um mesmo programa possa ser executado em 
diversas arquiteturas diferentes de computador, 
e popularizou diversos conceitos que acabaram 
sendo incorporados por outras linguagens poste-
riores, como as interfaces.
A Microsoft oferece hoje a família .NET, que possui 
linguagens de diferentes paradigmas, incluindo uma 
linguagem funcional, F#. Mas suas linguagens mais 
famosas são o C# (uma linguagem originalmente 
baseada no Java) e o VB.NET (uma linguagem 
baseada no VisualBasic). Dentre as vantagens 
estão a possibilidade de interação entre códigos 
escritos nas diferentes linguagens da família. Um 
programa em C# pode importar classes escritas 
em VB.NET, por exemplo.
1818
Várias linguagens de scripting (programas curtos 
para automatizar pequenas tarefas) populares 
também são orientadas a objeto, como o JavaScript 
(muito utilizado no desenvolvimento de páginas 
web) e o Python (popular na automação de tarefas 
e processamento de dados).
19
PROGRAMAÇÃO 
FUNCIONAL
Enquanto a programação orientada a objeto ainda 
se aproxima bastante da programação imperativa 
– afinal, apesar de adotarmos regras e estruturas 
específicas para auxiliar na modularização, nossos 
programas ainda armazenam dados em variáveis 
e seguem instruções sequenciais – outros pa-
radigmas podem ser radicalmente diferentes. O 
paradigma funcional é um deles.
Um programa escrito utilizando uma linguagem 
funcional é, em tese, mais legível, mais seguro e 
menos propenso a erros. A programação funcio-
nal é considerada mais determinística do que a 
programação imperativa.
Dizemos que um programa ou algoritmo é consi-
derado determinístico se, dadas as mesmas entra-
das, sempre produz as mesmas saídas. Por mais 
estranho que isso possa soar, programas escritos 
de maneira puramente imperativa ou orientada a 
objeto podem se comportar de maneira aparente-
mente não determinística.
Isso ocorre porque os programas (assim como os 
objetos) possuem um estado, que é dado pelos 
valores de todas as variáveis. Em projetos muito 
grandes, uma função pode ter diversas variáveis 
20
internas, além de interagir com variáveis externas 
através de parâmetros, referências, ou até mesmo 
variáveis globais – variáveis criadas fora de qualquer 
escopo e, portanto, passíveis de serem alteradas 
por qualquer função. 
Dessa maneira, uma função recebendo um mesmo 
parâmetro múltiplas vezes pode retornar valores 
diferentes, caso ela dependa de certas variáveis 
espalhadas pelo código. Essas variáveis acabam 
se comportando como entradas “ocultas” ou “im-
plícitas” das funções e fazem com que seja muito 
mais difícil analisar seu comportamento ou prever 
seu resultado final.
Um programa puramente funcional não possui 
estado. Ele não armazena dados em variáveis. Ele 
possui apenas funções, e essas funções tentam ao 
máximo seguir o comportamento de uma função 
matemática.
Isso torna sua análise bastante simples – afinal, 
funções matemáticas costumam ser expressões 
simples, sem estruturas como malhas de repetição. 
Além disso, devido à ausência de um estado que 
pode se alterar, elas são completamente deter-
minísticas e sempre produzirão a mesma saída 
para uma certa entrada.
Você provavelmente já estudou funções matemáticas 
no passado, mas vamos revisar brevemente alguns 
21
conceitos e partir deles para ver outros conceitos 
importantes para a programação funcional, como 
as funções lambda.
Funções matemáticas
Uma função matemática é uma regra para mape-
armos valores entre dois conjuntos: o conjunto 
domínio e o conjunto imagem. Cada valor do 
domínio pode ser mapeado para exatamente um 
valor da imagem. Um valor do domínio jamais 
poderá ser mapeado para múltiplos valores dife-
rentes da imagem.
Vamos tomar como exemplo a seguinte função:
f(x)=2x
Se o conjunto domínio for (1, 2, 3, 4), ao aplicarmos 
a regra, obteremos os valores (2, 4, 6, 8). Ou seja, 
a função f(x) mapeia os valores 1, 2, 3 e 4 para os 
valores 2, 4, 6 e 8, respectivamente.
Funções matemáticas podem ser constituídas 
por operações matemáticas, por outras funções, 
por chamadas para a própria função (chamamos 
de “chamadas recursivas”) ou até mesmo por 
expressões condicionais. Mas não temos outras 
estruturas comuns na programação imperativa, 
como as malhas de repetição. 
Um exemplo puramente matemático que envolve 
expressões condicionais e chamadas recursivas 
22
é a sequência de Fibonacci. Seus dois primeiros 
termos são iguais a 1. Qualquer outro termo é 
definido pela soma de seus dois antecessores ime-
diatos. Sendo assim, podemos definir a sequência 
de Fibonacci com a seguinte função:
𝐹𝐹 𝑛𝑛 = 	% 1, 𝑠𝑠𝑠𝑠	𝑛𝑛 = 0	𝑜𝑜𝑜𝑜	𝑛𝑛 = 1𝐹𝐹 𝑛𝑛 −1 +𝐹𝐹 𝑛𝑛 −2 , 𝑠𝑠𝑠𝑠	𝑛𝑛> 1
Note que é uma regra rigorosa muito bem defini-
da, fácil de ler e completamente determinística, 
com qualquer valor de entrada gerando sempre o 
mesmo valor de saída.
Funções compostas
Outra possibilidade que a matemática nos oferece 
é a composição de funções. Considere as funções 
abaixo:
f(x)=2x
g(x)=3x+2
Podemos definir uma função h(x) dada pela com-
posição de ambas as funções. A notação para a 
composição de funções é:
h=gof(x)
Essa notação significa:
h(x)=f(g(x))
Na prática, isso significa que o resultado de h(x) 
será determinado quando substituirmos “x” em 
23
f(x) por g(x) – ou seja, aplicar uma função como 
entrada ou parâmetro para outra função.
h(x)=2(3x+2)
h(x)=6x+4
Ao contrário das linguagens imperativas tradicio-
nais, funções podem receber outras funções (e não 
apenas valores, como variáveis, constantes ou o 
retorno de uma função) como parâmetro. 
Funções lambda
Quando estudamos funções ao falar sobre progra-
mação procedural, definimos funções como sendo 
subprogramas que possuem um nome, uma lista 
de parâmetros e, opcionalmente, um retorno.
A programação funcional nos permite ter funções 
sem nome. Chamamos essas funções anônimas 
de funções lambda.
Isso abre possibilidades impensáveis nas linguagens 
imperativas tradicionais. Uma função pode criar 
novas funções. Podemos, inclusive, ter funções 
que retornam funções.
Como não precisamos usar um comando para de-
finir previamente e dar um nome a uma função em 
um bloco de comandos avulso, é possível no meio 
de uma função surgir a definição de um lambda 
em função de diferentes condições e resultados 
24
de outras funções, permitindo a “geração” de uma 
função nova dinamicamente.
Linguagens funcionais
A primeira linguagem de programação funcional 
foi a LISP. Ela foi proposta por John McCarthy, do 
MIT, ainda no final da década de 50 como uma ten-
tativa de descrever matematicamente programas 
de computador.
Um de seus conceitos-chave era a função EVAL, 
que serviria para avaliar qualquer outra expressão. 
Dois pesquisadores, Stephen B. Russell e Daniel J. 
Edwards concluíram que uma implementação real 
da função EVAL seria, na prática, um interpretador 
de LISP – ou seja, um programa de computador 
que executa programas escritos em LISP. Eles 
conseguiram realizar essa implementação com 
sucesso.
O interpretador LISP, apesar de nos círculos de 
programadores ter a reputação de ter uma sinta-
xe supostamente complicada por conta do uso 
frequente de parênteses aninhados, possui uma 
sintaxe tão simples que é possível escrever de 
maneira compacta uma implementação da função 
EVAL em LISP, o que na prática significa que é fácil 
escrever um interpretador LISP em LISP.
25
Apesar de a programação funcional existir desde a 
época das primeiras linguagens procedurais e ter 
tantos benefícios, ela nunca se tornou o paradig-
ma dominante no mercado. Notamos até hoje um 
domínio muito grande de paradigmas relacionados 
ao imperativo, principalmente a programação orien-
tada a objeto, emboraatualmente a procura por 
desenvolvedores familiares com esse paradigma 
esteja em alta por conta da adoção de linguagens 
funcionais em certos sistemas que devem ser 
extremamente determinísticos e rigorosos, como 
sistemas bancários.
As linguagens funcionais mais utilizadas atualmente 
não são puramente funcionais. É comum que elas 
incorporem alguns elementos imperativos, mas 
utilizem predominantemente conceitos típicos 
da programação imperativa. Várias linguagens 
funcionais são consideradas dialetos de LISP, 
como Common Lisp, Clojure e Scheme, enquanto 
outras foram fortemente influenciadas direta ou 
indiretamente por ela, como Elixir, Haskell e F#.
Além disso, várias linguagens utilizadas predomi-
nantemente para o desenvolvimento em outros 
paradigmas, como o orientado a objeto, incorpo-
raram alguns conceitos de programação funcional, 
como o C++, Java, JavaScript, Go, Rust e Python. 
O Python, por exemplo, suporta funções lambda.
2626
PROGRAMAÇÃO LÓGICA
O nome “programação lógica” pode soar confu-
so em um primeiro contato: todas as formas de 
programação usam lógica, correto? Então o que 
diferencia esse paradigma dos outros?
O paradigma lógico é fortemente baseado na ló-
gica formal – um campo da matemática que lida 
com provas formais, argumentação e teoria de 
conjuntos, entre outras coisas. Esse paradigma 
é declarativo, significando que seus programas 
não são formados por instruções, e sim por um 
conjunto de afirmações.
Um programa será constituído por uma série de 
regras ou afirmações, chamados de predicados. 
Essas regras podem relacionar diferentes dados.
O programa será executado por uma espécie de 
loop infinito, onde o usuário pode fazer consultas. 
O resultado dessas consultas é exibido imediata-
mente na tela e em seguida o programa torna a 
aguardar novas consultas.
Uma consulta pode ser uma afirmação lógica. O 
programa testará todas as combinações possíveis 
de dados e relações declarados em seus predica-
dos para determinar se a consulta é verdadeira ou 
falsa, ou para quais valores ela é verdadeira, e irá 
fornecer essa resposta.
2727
Temos poucos exemplos de linguagens de progra-
mação lógica, sendo a mais conhecida o PROLOG 
e seus dialetos. Essa linguagem teve popularidade 
limitada em meios acadêmicos, principalmente 
dentre pesquisadores interessados na automação 
de produção de provas formais, simplificação de 
predicados lógicos e também em formas diferentes 
de representar conhecimento em alguns modelos 
de inteligência artificial.
28
PROGRAMAÇÃO 
ORIENTADA A EVENTOS
A programação orientada a eventos é uma for-
ma de planejar um programa que precise reagir 
a diferentes ocorrências com frequência. Essas 
ocorrências podem ser as mais diversas: uma 
interação do usuário, um sinal captado por um 
sensor ou mesmo uma comunicação de outro 
processo ou thread.
Um caso fácil de visualizar esse paradigma é em 
programas que possuem uma interface gráfica. 
Considere, por exemplo, um editor de texto. Quan-
do abrimos um editor de texto, ele não faz muita 
coisa. É normal que ele apenas mostre uma tela 
em branco (ou um texto parcialmente preenchido, 
quando abrimos um arquivo pronto) e um cursor 
piscando em uma certa posição. Ele pode passar 
horas a fio nesse estado, sem realizar nenhuma 
ação interessante.
Quando pressionamos uma tecla, porém, ele colo-
ca a letra correspondente a essa tecla na posição 
onde o cursor estava piscando. Também podemos 
clicar em botões: se você clica no botão de salvar, 
por exemplo, o programa mostra um pop-up para 
você escolher a pasta e dar um nome ao arquivo.
29
Um evento é uma notificação de que algo ocorreu. 
Essa notificação pode partir do sistema operacio-
nal, do hardware ou de outros programas que se 
comunicam com o nosso. 
O programa orientado a eventos tipicamente possui 
um loop principal que mantém o programa aberto 
(ou um sistema onde o programa pode entrar em 
espera e ser notificado e reativado quando um 
evento ocorre) e uma série de tratamentos para 
eventos específicos. Os tratamentos são funções 
que serão executadas sempre que um evento es-
pecífico ocorrer.
Em um programa com interface gráfica – como 
o editor de texto que utilizamos como exemplo – 
existem diferentes elementos gráficos (frequente-
mente objetos, em linguagens orientadas a objeto), 
conhecidos como widgets. Cada widget possui uma 
funcionalidade específica. Interações com esse 
widget geram eventos, e esses eventos provocam 
a execução do tratamento para esse evento, que 
normalmente é a funcionalidade do widget.
A programação orientada a evento não costuma 
aparecer “sozinha” e é um recurso utilizado em 
conjunto com outros paradigmas, como proce-
dural (através de funções que tratam eventos) ou 
orientado a objeto (em que até mesmo os eventos 
podem ser objetos). Ela pode ser implementada 
30
em diversas linguagens diferentes e depende 
mais de recursos disponíveis (como um sistema 
de interrupção por hardware ou um sistema de 
notificação do sistema operacional) do que de 
recursos da linguagem.
3131
PROGRAMAÇÃO 
CONCORRENTE
Um aspecto cada vez mais presente na computa-
ção é a realização de diferentes tarefas ao mesmo 
tempo. O seu navegador de internet, por exemplo, 
precisa trocar mensagens com o servidor, executar 
scripts, realizar interação com o usuário etc.
Em computadores com um único processador, essas 
diferentes tarefas precisariam necessariamente 
se revezar: uma delas ocupa o processador por 
uma pequena quantidade de tempo, e em seguida 
o libera para que outra tarefa o ocupe. 
Atualmente, vários computadores possuem múlti-
plos processadores. Isso pode ocorrer em grandes 
sistemas presentes em empresas que utilizam ou 
vendem serviços de computação “pesada”, como 
Amazon e Google, ou mesmo em dispositivos de 
uso pessoal: desktops, laptops, e até mesmo vários 
modelos de smartphone comumente oferecem 
múltiplos processadores.
Isso permite que diversas tarefas sejam realizadas 
de maneira paralela, ou seja, ao mesmo tempo. No 
exemplo do navegador, é possível, por exemplo, que 
um núcleo de processamento cuide da comunica-
ção com o servidor, enquanto outro núcleo executa 
scripts, e assim sucessivamente.
3232
Projetar programas para execução paralela, ou 
concorrente, exige suporte por parte do sistema 
operacional, que fará boa parte das tarefas de ge-
renciamento e oferecerá recursos para sincroniza-
ção, controle, distribuição das tarefas e resolução 
de alguns problemas comuns que discutiremos 
em breve. 
Também é importante que a linguagem utilizada 
ofereça algum nível de suporte para a programa-
ção multiprocessada. Linguagens de diferentes 
paradigmas oferecem esse recurso em diferentes 
níveis. Linguagens como C++ (orientada a objeto), 
C e FORTRAN (procedurais) possuem bibliotecas 
para permitir essa implementação. Outras, como 
F# (funcional), Java, C# e Python (orientadas a 
objeto) oferecem recursos de forma nativa.
Utilizar esses recursos para criar programas que 
executam de maneira paralela oferece alguns 
benefícios, sendo o mais óbvio deles o desempe-
nho: se diferentes partes de um programa forem 
executadas simultaneamente por processadores 
diferentes, o programa terminará de executar mais 
rápido do que se todas as partes fossem executa-
das sequencialmente por um único processador. 
Imagine quatro pessoas empacotando compras 
de supermercado – cada um colocando diferentes 
mercadorias em diferentes sacolas – e compare 
3333
com uma pessoa sozinha empacotando as mes-
mas mercadorias.
Porém, nem todo tipo de tarefa pode ser paralelizado, 
e por isso nem todos os programas conseguirão 
se beneficiar diretamente da programação con-
corrente. Além disso, a programação concorrente 
também pode criar alguns problemas que não 
temos quando temos um único programa sendo 
executado de maneira linear. Vamos definir alguns 
conceitos básicos relacionados à programação 
concorrente, enunciar alguns dos problemas mais 
comuns e descrever as principais soluçõespara 
eles. Todos esses conceitos devem ser estudados 
de maneira mais aprofundada em disciplinas ligadas 
a sistemas operacionais ou à própria programação 
concorrente.
Conceitos básicos
Os sistemas operacionais costumam trabalhar 
com o conceito de processo. Um processo é 
uma abstração para um programa em execução. 
Ele possui um cabeçalho com informações de 
controle e uma região de código, que são as suas 
instruções.
É possível, porém, que um processo possua 
diversos blocos de código para serem executa-
dos paralelamente. Todos eles compartilham o 
mesmo cabeçalho, pois são o mesmo programa. 
3434
Mas eles podem ser executados por núcleos de 
processamento diferentes. Chamamos esses có-
digos paralelos de threads. E um programa que 
trabalha com múltiplos threads pode ser chamado 
de multithread.
Quando possuímos mais threads do que núcleos 
disponíveis, é preciso que haja um mecanismo de 
controle para permitir que cada tarefa seja execu-
tada sem que nenhuma delas monopolize o uso do 
processador. Os sistemas operacionais costumam 
vir com um escalonador de tarefas para realizar 
esse controle, que tipicamente permitem que uma 
tarefa seja executada por um instante de tempo, 
em seguida eles a pausam e permitem que outra 
tarefa também seja executada por um instante 
de tempo, e assim sucessivamente. Tempo não 
é o único critério: algumas tarefas podem pos-
suir prioridade maior do que outras. Além disso, 
quando uma tarefa precisa aguardar algo (como 
uma operação de entrada e saída de dados ou o 
resultado de outra tarefa), ela é colocada em espera 
e o processador é liberado para outras tarefas.
Problemas de concorrência
Imagine duas tarefas distintas (por exemplo, duas 
funções sendo executadas em paralelo) e ambas 
possuem instruções para manipular um mesmo 
valor na memória. É possível que a ordem que duas 
3535
operações são realizadas afete o resultado. Sendo 
assim, é difícil prever o resultado da operação se 
não temos controle sobre qual tarefa conseguirá 
ser executada primeiro. Chamamos esse problema 
de condição de corrida.
Imagine agora outro caso. Imagine que existam 
dois recursos – dados na memória, dispositivos 
de entrada/saída ou outro motivo qualquer – e 
duas tarefas sendo executadas. Uma das tarefas 
possui, no momento, acesso a um dos recursos, 
e a outra tarefa possui acesso ao outro recurso. 
Imagine que a primeira tarefa precisa de ambos 
os recursos para sua execução, e irá liberar ambos 
apenas após o seu final. O que ocorre se a outra 
tarefa também precisa de ambos os recursos e 
só irá liberá-los após o final de sua execução? 
Ambas as tarefas ficarão eternamente cada uma 
bloqueando um dos recursos e esperando que 
a outra libere o outro recurso. Chamamos essa 
situação de deadlock. 
Gerenciando a sincronia
Algumas soluções diferentes são adotadas para 
evitar problemas com a condição de corrida ou 
com o deadlock. Cada um deles será melhor em 
diferentes casos e é até possível utilizar alguma 
dessas soluções para implementar a outra (ex: uti-
lizar um monitor para implementar um semáforo).
3636
A técnica mais simples é um semáforo. Semáforos 
podem ser contadores ou binários. Um semáforo 
do tipo contador serve quando temos diversos 
recursos disponíveis – por exemplo, regiões de 
memória para armazenar dados. Toda tarefa que 
for consumir esses recursos deve incrementar o 
contador do semáforo indicando a quantidade de 
recursos sendo utilizada. Ao finalizar sua execução 
e liberar os recursos, ela deve decrementar o con-
tador de acordo. Os binários são mais simples, e 
possuem apenas dois valores possíveis: o recurso 
está ocupado ou livre. Antes de usar um recurso, a 
tarefa deve verificar se ele está livre. Caso esteja, 
ela o marca como ocupado e o usa. Ao final de sua 
execução, torna a sinalizá-lo como livre.
A próxima técnica é o monitor. Um monitor é uma 
estrutura que contém um mutex (uma trava) para 
o recurso a ser utilizado e coleções de tarefas in-
teressadas nos recursos, bem como informações 
pertinentes para gerenciar as tarefas. Se uma 
tarefa utilizando um recurso precisa aguardar 
alguma condição, o monitor irá liberar o recurso 
para outra tarefa.
Por fim, pode ser necessário que duas tarefas 
concorrentes conversem entre si. Para isso existe 
a troca de mensagens, que pode ser síncrona ou 
assíncrona. Existem várias formas diferentes de 
implementar essas mensagens. Mas de maneira 
3737
simplificada, na comunicação síncrona é neces-
sário que ambas as tarefas estejam disponíveis 
para comunicação, de modo que uma tarefa não 
interrompa a execução da outra, enquanto na assín-
crona uma tarefa pode transmitir uma mensagem 
e prosseguir com sua execução sem aguardar 
resposta, e a outra responderá quando possível.
38
CONSIDERAÇÕES FINAIS
Um programa pode ser pensado de muitas maneiras 
diferentes. Uma sequência de instruções, apesar 
de ser a forma mais natural para as arquiteturas 
de hardware que utilizamos em nosso dia a dia, 
não é a única e nem necessariamente a melhor 
para resolver certos tipos de problemas.
Diferentes paradigmas de programação, ou for-
mas de estruturar e modelar nossos programas, 
serão mais adequados em diferentes situações 
ou contextos. 
Nossos programas podem ser imperativos, sendo 
constituídos apenas por diversas instruções se-
quenciais. Essas instruções podem ser agrupadas 
de maneiras diferentes, como em subprogramas 
(ou funções), caracterizando o paradigma procedu-
ral, ou podemos ir além, criando uma modelagem 
complexa de todas as entidades envolvidas em 
um problema através das classes e objetos, e as 
instruções ficam encapsuladas como ações dos 
objetos (ou métodos), caracterizando a programa-
ção orientada a objeto.
Alternativamente, nossos programas podem ser 
declarativos: ao invés de instruções sequenciais, 
eles podem ser formados por funções matemáticas 
rigorosamente definidas. Essas funções podem, 
39
inclusive, gerar outras funções, graças a recursos 
como as funções lambda. Isso caracteriza a pro-
gramação funcional, extremamente determinística 
e segura. Alternativamente, as “declarações” podem 
ser predicados lógicos, e o programa em execução 
pode avaliar a validade de novas inferências a partir 
desses predicados, a chamada programação lógica.
Muitas vezes um programa pode combinar ele-
mentos de diferentes paradigmas – por exemplo, 
funções definidas com todo o rigor e recursos da 
programação funcional, mas um bloco principal 
de instruções imperativas que irá interagir com 
essas funções.
Em alguns contextos específicos, um programa 
em qualquer um desses paradigmas poderá tam-
bém incorporar elementos de outros paradigmas 
relacionados a restrições ou especificações de 
casos específicos de uso. Por exemplo, um pro-
grama escrito em diferentes paradigmas pode 
incorporar conceitos da programação orientada a 
eventos, ficando preparado para tratar diferentes 
ocorrências, como uma interação do usuário como 
uma interface gráfica (um clique em um botão, 
por exemplo).
De maneira geral, o paradigma a ser adotado deve 
ser suportado pela linguagem, pois frequentemente 
sua implementação bem-sucedida dependerá de 
40
recursos que podem ou não ser oferecidos pela 
linguagem. A linguagem C, uma das linguagens 
procedurais mais conhecidas, não pode ser utili-
zada para a programação orientada a objeto, por 
exemplo, já que não oferece os recursos clássicos 
desse paradigma, como objetos, classes, herança. 
Tampouco pode ser utilizada para a programação 
funcional, já que não oferece recursos como as 
funções lambda, limitando muito o que uma função 
pode ou não fazer.
Em outros casos, não basta a linguagem oferecer 
um recurso: o próprio sistema operacional deverá 
oferecer esses recursos, e a linguagem será, na 
prática, uma interface para acessarmos esses 
recursos do sistema. É o caso da programação 
concorrente, ou programação paralela, quando 
desenhamos nosso programa para subdividir suas 
instruções em diferentes tarefas que podemser 
executadas de maneira simultânea por diferentes 
processadores. Essa subdivisão pode gerar dife-
rentes problemas, e precisaremos de recursos 
adequados para tratá-los e garantir que estamos 
nos beneficiando de verdade dessa técnica, e não 
criando problemas graves que podem resultar em 
travamentos ou erros de cálculo.
Um bom programador não deve ter linguagens ou 
paradigmas “de estimação”. Uma linguagem de 
programação deve ser vista como uma ferramenta. 
41
Tanto o martelo quanto o alicate são ferramentas 
úteis, mas cada uma é muito boa em sua tarefa e 
pouco útil para realizar a tarefa da outra. O mesmo 
ocorre com linguagens, e é estudando diferentes 
paradigmas e nos aventurando em diferentes 
linguagens que conseguiremos perceber na prá-
tica as vantagens e desvantagens de cada uma 
e desenvolver o espírito crítico necessário para 
escolher qual delas utilizaremos em cada um de 
nossos projetos futuros.
Referências Bibliográficas 
& Consultadas
CORREA, A. G. D. Programação I. São Paulo: 
Pearson, 2015. [Biblioteca Virtual].
FELIX, R. (Org.). Programação orientada a 
objetos. São Paulo: Pearson, 2016. [Biblioteca 
Virtual].
LEAL, G. C. L. Linguagem, programação e 
banco de dados: guia prático de aprendizagem. 
Curitiba: Intersaberes, 2015. [Biblioteca Virtual]. 
SANTOS, M. G. dos; SARAIVA, M. de O.; 
GONÇALVES, P. de F. Linguagem de 
programação. Porto Alegre: SAGAH, 2018. 
[Minha Biblioteca].
SEBESTA, R. W. Conceitos de linguagens de 
programação. 11. ed. Porto Alegre: Bookman, 
2018. [Minha Biblioteca]. 
SILVA, E. A. da. Introdução às linguagens de 
programação para CLP. São Paulo: Blucher, 
2016. [Biblioteca Virtual].
SILVA, F. M. da; LEITE, M. C. D.; OLIVEIRA, D. B. 
de. Paradigmas de programação. Porto Alegre: 
SAGAH, 2019. [Minha Biblioteca].
TUCKER, A.; NOONAN, R. Linguagens de 
programação: princípios e paradigmas. 2. ed. 
Porto Alegre: AMGH, 2014. [Minha Biblioteca].
	Introdução
	Paradigmas de programação
	Programação Imperativa
	Programação Orientada a Objeto
	Programação Funcional
	Programação Lógica
	Programação Orientada a Eventos
	Programação Concorrente
	Considerações finais
	Referências Bibliográficas & Consultadas

Mais conteúdos dessa disciplina