Pesquisa · Mapa mental

Programação funcional

Em ciência da computação, programação funcional é um padrão de programação que trata a computação como uma avaliação de funções matemáticas, evitando estados ou dados mutáveis. Ela enfatiza a aplicação de funções, em contraste da programação imperativa. Enfatizando as expressões ao invés de comandos, as expressões são utilizados para cálculo de valores com dados imutáveis.

Fonte: Wikipédia (pt)Atualizado em 25/07/2026
01

História

Imagem: Agência Brasília · BY · Openverse

O cálculo lambda, desenvolvido na década de 1930 por Alonzo Church, é um sistema formal de computação construído a partir da aplicação de funções. Em 1937, Alan Turing provou que o cálculo lambda e as máquinas de Turing são modelos de computação equivalentes, mostrando que o cálculo lambda é Turing completo. O cálculo lambda forma a base de todas as linguagens de programação funcionais. Uma formulação teórica equivalente, a lógica combinatória, foi desenvolvida por Moses Schönfinkel e Haskell Curry nas décadas de 1920 e 1930. Mais tarde, Church desenvolveu um sistema mais fraco, o cálculo lambda simplesmente tipado, que estendeu o cálculo lambda atribuindo um tipo de dados a todos os termos. Isto forma a base para a programação funcional tipada estaticamente. A primeira linguagem de programação de alto nível funcional, Lisp, foi desenvolvida no final da década de 1950 para a série de computadores científicos IBM 700/7000 por John McCarthy enquanto estava no Instituto de Tecnologia de Massachusetts (MIT). As funções Lisp foram definidas usando a notação lambda de Church, estendida com uma construção de rótulo para permitir funções recursivas. O Lisp introduziu pela primeira vez muitas características paradigmáticas da programação funcional, embora os primeiros Lisps fossem linguagens multiparadigma e incorporassem suporte para vários estilos de programação à medida que novos paradigmas evoluíam. Dialetos posteriores, como Scheme e Clojure, e ramificações como Dylan e Julia, procuraram simplificar e racionalizar o Lisp em torno de um núcleo puramente funcional, enquanto o Common Lisp foi projetado para preservar e atualizar as características paradigmáticas dos numerosos dialetos mais antigos que substituiu.

02

Conceitos

Imagem: Guilmann · BY-SA · Openverse

Uma série de conceitos e paradigmas são específicos à programação funcional e, em geral, estranhos à programação imperativa (incluindo a programação orientada a objetos). No entanto, as linguagens de programação frequentemente atendem a vários paradigmas de programação, de modo que os programadores que usam linguagens "maioritariamente imperativas" podem ter utilizado alguns destes conceitos. As estruturas de dados puramente funcionais são frequentemente representadas de uma forma diferente das suas contrapartes imperativas. Por exemplo, o arranjo (array) com tempos de acesso e atualização constantes é um componente básico da maioria das linguagens imperativas, e muitas estruturas de dados imperativas, como a tabela de hash e o heap binário, baseiam-se em arranjos. Os arranjos podem ser substituídos por mapas ou listas de acesso aleatório, que admitem implementação puramente funcional, mas têm tempos de acesso e atualização logarítmicos. As estruturas de dados puramente funcionais têm persistência, uma propriedade de manter as versões anteriores da estrutura de dados inalteradas. Em Clojure, estruturas de dados persistentes são usadas como alternativas funcionais às suas contrapartes imperativas. Vetores persistentes, por exemplo, usam árvores para atualização parcial. Chamar o método de inserção resultará na criação de alguns, mas não de todos os nós.

Funções de primeira classe e de ordem superior

As funções de ordem superior são funções que podem receber outras funções como argumentos ou retorná-las como resultados. No cálculo, um exemplo de uma função de ordem superior é o operador diferencial d / d x {\displaystyle d/dx} , que retorna a derivada de uma função f {\displaystyle f} . As funções de ordem superior estão intimamente relacionadas com as funções de primeira classe, uma vez que tanto as funções de ordem superior como as funções de primeira classe permitem funções como argumentos e resultados de outras funções. A distinção entre as duas é sutil: "ordem superior" descreve um conceito matemático de funções que operam sobre outras funções, enquanto "primeira classe" é um termo da ciência da computação para entidades de linguagem de programação que não têm restrições quanto ao seu uso (assim, funções de primeira classe podem aparecer em qualquer lugar do programa onde outras entidades de primeira classe, como números, podem, incluindo como argumentos para outras funções e como os seus valores de retorno).

Funções puras

As funções puras (ou expressões) não têm efeitos colaterais (na memória ou I/O). Isto significa que as funções puras têm várias propriedades úteis, muitas das quais podem ser usadas para otimizar o código: Embora a maioria dos compiladores para linguagens de programação imperativas detete funções puras e realize a eliminação de subexpressões comuns para chamadas de funções puras, nem sempre conseguem fazê-lo para bibliotecas pré-compiladas, que geralmente não expõem esta informação, impedindo assim otimizações que envolvam essas funções externas. Alguns compiladores, como o gcc, adicionam palavras-chave extras para que um programador marque explicitamente as funções externas como puras, para permitir tais otimizações. O Fortran 95 também permite que as funções sejam designadas como puras (pure). O C++11 adicionou a palavra-chave constexpr com uma semântica semelhante.

Recursividade

A iteração (repetição/laços) em linguagens funcionais é geralmente realizada através da recursividade. As funções recursivas invocam-se a si próprias, permitindo que uma operação seja repetida até atingir o caso base. Em geral, a recursividade requer a manutenção de uma pilha de chamadas (call stack), que consome espaço num montante linear em relação à profundidade da recursividade. Isto poderia tornar a recursividade proibitivamente cara de usar em vez de laços imperativos. No entanto, uma forma especial de recursividade conhecida como recursividade de cauda pode ser reconhecida e otimizada por um compilador para o mesmo código usado para implementar a iteração em linguagens imperativas. A otimização da recursividade de cauda pode ser implementada transformando o programa em estilo de passagem de continuação durante a compilação, entre outras abordagens.

Avaliação estrita vs não estrita

As linguagens funcionais podem ser categorizadas por utilizarem a avaliação estrita (ansiosa) ou não estrita (preguiçosa), conceitos que se referem a como os argumentos da função são processados quando uma expressão está a ser avaliada. A diferença técnica está na semântica denotacional de expressões que contêm computações falhas ou divergentes. Sob a avaliação estrita, a avaliação de qualquer termo que contenha um sub-termo falho, falha. Por exemplo, a declaração em Python: falha sob a avaliação estrita devido à divisão por zero no terceiro elemento da lista. Sob a avaliação preguiçosa, a função de comprimento (length) retorna o valor 4 (ou seja, o número de itens na lista), uma vez que avaliá-la não tenta avaliar os termos que compõem a lista. Resumindo, a avaliação estrita avalia sempre totalmente os argumentos da função antes de invocar a função. A avaliação preguiçosa não avalia os argumentos da função a menos que os seus valores sejam necessários para avaliar a própria chamada da função.

Sistemas de tipos

Especialmente desde o desenvolvimento da inferência de tipos Hindley-Milner na década de 1970, as linguagens de programação funcional tenderam a usar o cálculo lambda tipado, rejeitando todos os programas inválidos em tempo de compilação e arriscando erros de falso positivo, em oposição ao cálculo lambda não tipado, que aceita todos os programas válidos em tempo de compilação e arrisca erros de falso negativo, usado no Lisp e suas variantes (como Scheme), uma vez que rejeitam todos os programas inválidos em tempo de execução quando a informação é suficiente para não rejeitar programas válidos. O uso de tipos de dados algébricos torna a manipulação de estruturas de dados complexas conveniente; a presença de verificação de tipos forte em tempo de compilação torna os programas mais fiáveis na ausência de outras técnicas de fiabilidade como o desenvolvimento orientado por testes/test-driven development, enquanto a inferência de tipos liberta o programador da necessidade de declarar manualmente os tipos ao compilador na maioria dos casos.

Transparência referencial

Os programas funcionais não têm declarações de atribuição, isto é, o valor de uma variável num programa funcional nunca muda depois de definido. Isto elimina quaisquer hipóteses de efeitos colaterais, porque qualquer variável pode ser substituída pelo seu valor real em qualquer ponto da execução. Por conseguinte, os programas funcionais são referencialmente transparentes. Considere a declaração de atribuição em C x = x * 10; isto muda o valor atribuído à variável x. Digamos que o valor inicial de x era 1, então duas avaliações consecutivas da variável x produzem 10 e 100, respetivamente. Claramente, substituir x = x * 10 por 10 ou 100 dá a um programa um significado diferente, e por isso a expressão não é referencialmente transparente. Na verdade, as declarações de atribuição nunca são referencialmente transparentes.

03

Comparação com a programação imperativa

Imagem: Senado Federal · BY · Openverse

A programação funcional é muito diferente da programação imperativa. As diferenças mais significativas decorrem do facto de a programação funcional evitar efeitos colaterais, que são usados na programação imperativa para implementar estado e I/O. A programação funcional pura evita completamente os efeitos colaterais e proporciona transparência referencial. As funções de ordem superior são raramente utilizadas na programação imperativa mais antiga. Um programa imperativo tradicional pode usar um laço para percorrer e modificar uma lista. Um programa funcional, por outro lado, provavelmente usaria uma função de ordem superior "map" que recebe uma função e uma lista, gerando e retornando uma nova lista através da aplicação da função a cada item da lista.

Programação imperativa vs. funcional

Os dois exemplos a seguir (escritos em Java) alcançam o mesmo efeito: multiplicam todos os números pares num arranjo por 10 e somam-nos todos, armazenando a soma final na variável result. Programação funcional com funções de ordem superior: Por vezes, as abstrações oferecidas pela programação funcional podem conduzir ao desenvolvimento de um código mais robusto que evita certos problemas que podem surgir quando se constrói sobre uma grande quantidade de código imperativo complexo, tais como erros de off-by-one (ver Décima regra de Greenspun).

Simulação de estado

Existem tarefas (por exemplo, a manutenção do saldo de uma conta bancária) que muitas vezes parecem mais naturalmente implementadas com recurso a estado. A programação funcional pura executa estas tarefas, bem como tarefas de I/O, como aceitar a entrada do utilizador e imprimir no ecrã, de uma forma diferente. A linguagem de programação funcional pura Haskell implementa-as utilizando mônadas (monads), derivadas da teoria das categorias. As mônadas oferecem uma forma de abstrair certos tipos de padrões computacionais, incluindo (mas não se limitando a) a modelagem de computações com estado mutável (e outros efeitos colaterais como I/O) de forma imperativa sem perder a pureza. Embora as mônadas existentes possam ser fáceis de aplicar num programa, dados modelos e exemplos apropriados, muitos estudantes acham-nas difíceis de compreender conceptualmente, por ex., quando lhes é pedido para definir novas mônadas (o que por vezes é necessário para certos tipos de bibliotecas).

Problemas de eficiência

As linguagens de programação funcional são tipicamente menos eficientes no seu uso da CPU e da memória do que as linguagens imperativas como C e Pascal. Isto está relacionado com o facto de algumas estruturas de dados mutáveis, como arranjos, terem uma implementação muito direta usando o hardware atual. Arranjos planos podem ser acessados de forma muito eficiente com CPUs profundamente segmentadas (pipelined), pré-buscadas eficientemente através de caches (sem busca de ponteiros complexa), ou manuseadas com instruções SIMD. Também não é fácil criar as suas contrapartes imutáveis de propósito geral igualmente eficientes. Para linguagens puramente funcionais, a lentidão (slowdown) no pior caso é logarítmica no número de células de memória usadas, porque a memória mutável pode ser representada por uma estrutura de dados puramente funcional com tempo de acesso logarítmico (como uma árvore balanceada). No entanto, essa lentidão não é universal. Para programas que realizam cálculos numéricos intensivos, linguagens funcionais como OCaml e Clean são apenas um pouco mais lentas que o C, de acordo com o The Computer Language Benchmarks Game. Para programas que lidam com grandes matrizes e bancos de dados multidimensionais, linguagens funcionais de arranjos (como J e K) foram desenhadas com otimizações de velocidade.

Programação funcional em linguagens não funcionais

É possível usar um estilo de programação funcional em linguagens que não são tradicionalmente consideradas linguagens funcionais. Por exemplo, tanto o D quanto o Fortran 95 suportam explicitamente funções puras. O JavaScript, Lua, Python e Go tiveram funções de primeira classe desde o seu início. O Python tinha suporte para "lambda", "map", "reduce" e "filter" em 1994, bem como fechamentos (closures) no Python 2.2, embora o Python 3 tenha relegado o "reduce" para o módulo functools da biblioteca padrão. As funções de primeira classe foram introduzidas em outras linguagens populares como Perl 5.0 em 1994, PHP 5.3, Visual Basic 9, C# 3.0, C++11 e Kotlin.

04

Comparação com a programação em lógica

Imagem: Senado Federal · BY · Openverse

A programação em lógica pode ser vista como uma generalização da programação funcional, na qual as funções são um caso especial de relações. Por exemplo, a função, mother(X) = Y, (cada X tem apenas uma mãe Y) pode ser representada pela relação mother(X, Y). Enquanto as funções têm um padrão estrito de entrada-saída para os argumentos, as relações podem ser consultadas com qualquer padrão de entradas e saídas. Considere o seguinte programa em lógica: O programa pode ser consultado, tal como um programa funcional, para gerar mães a partir dos filhos: Mas também pode ser consultado de trás para a frente, para gerar filhos: Pode até mesmo ser utilizado para gerar todas as instâncias da relação de mãe: Em comparação com a sintaxe relacional, a sintaxe funcional é uma notação mais compacta para funções aninhadas. Por exemplo, a definição de avó materna na sintaxe funcional pode ser escrita na forma aninhada:

05

Aplicações

Imagem: Senado Federal · BY · Openverse

Editores de texto

O Emacs, uma família de editores de texto altamente extensível, utiliza o seu próprio dialeto Lisp para a escrita de plugins. O autor original da implementação mais popular do Emacs, o GNU Emacs e o Emacs Lisp, Richard Stallman, considera o Lisp uma das suas linguagens de programação favoritas.

Folhas de cálculo

As folhas de cálculo podem ser consideradas uma forma de sistema de programação funcional puro, de avaliação estrita e de ordem zero (sem funções de ordem superior). No entanto, as folhas de cálculo carecem geralmente de funções de ordem superior, bem como de reutilização de código, e nalgumas implementações, carecem também de recursividade. Foram desenvolvidas várias extensões para programas de folhas de cálculo para permitir funções de ordem superior e reutilizáveis, mas até agora continuam a ter uma natureza predominantemente acadêmica.

Microsserviços

Devido à sua capacidade de composição, os paradigmas de programação funcional podem ser adequados para arquiteturas baseadas em microsserviços.

Mundo acadêmico

A programação funcional é uma área ativa de investigação no campo da teoria das linguagens de programação. Existem vários espaços de publicação revistos por pares focados na programação funcional, incluindo a Conferência Internacional de Programação Funcional (ICFP), o Journal of Functional Programming e o Simpósio sobre Tendências em Programação Funcional (TFP).

Indústria

A programação funcional tem sido empregue numa vasta gama de aplicações industriais. Por exemplo, o Erlang, desenvolvido pela empresa sueca Ericsson no final da década de 1980, foi inicialmente utilizado para implementar sistemas de telecomunicações tolerantes a falhas, mas desde então tornou-se popular para a construção de diversas aplicações em empresas como a Nortel, Facebook, Électricité de France e WhatsApp. O Scheme, um dialeto do Lisp, foi utilizado como base para várias aplicações nos primeiros computadores Apple Macintosh e tem sido aplicado a problemas como software de simulação para treinos e controlo de telescópios. O OCaml, introduzido em meados da década de 1990, tem visto uso comercial em áreas como análise financeira, verificação de controladores de software, programação de robôs industriais e análise estática de software incorporado. O Haskell, embora inicialmente concebido como uma linguagem de pesquisa, também tem sido aplicado em áreas como sistemas aeroespaciais, conceção de hardware e programação web.

Educação

Muitas universidades ensinam programação funcional. Algumas tratam-na como um conceito introdutório à programação, enquanto outras ensinam primeiro métodos de programação imperativa. Fora da ciência da computação, a programação funcional é usada para ensinar resolução de problemas e conceitos algébricos e geométricos. Tem sido também usada para ensinar mecânica clássica, como no livro Structure and Interpretation of Classical Mechanics. Em particular, o Scheme tem sido uma escolha relativamente popular para o ensino da programação durante anos.

Vídeos recomendados

Fontes consultadas

Continue pesquisando