Programação funcional
Na ciência da computação, programação funcional é um paradigma de programação em que os programas são construídos aplicando e compondo funções. É um paradigma de programação declarativa no qual as definições de função são árvores de expressões que mapeiam valores para outros valores, em vez de uma sequência de declarações imperativas que atualizam o estado de execução do programa.
Na programação funcional, as funções são tratadas como cidadãos de primeira classe, o que significa que podem ser vinculadas a nomes (incluindo identificadores locais), passadas como argumentos e retornadas de outras funções, assim como qualquer outro tipo de dados. Isso permite que os programas sejam escritos em um estilo declarativo e combinável, onde pequenas funções são combinadas de maneira modular.
A programação funcional às vezes é tratada como sinônimo de programação puramente funcional, um subconjunto da programação funcional que trata todas as funções como funções matemáticas determinísticas ou funções puras. Quando uma função pura é chamada com alguns argumentos fornecidos, ela sempre retornará o mesmo resultado e não pode ser afetada por nenhum estado mutável ou outros efeitos colaterais. Isso contrasta com os procedimentos impuros, comuns na programação imperativa, que podem ter efeitos colaterais (como modificar o estado do programa ou receber informações de um usuário). Os defensores da programação puramente funcional afirmam que, ao restringir os efeitos colaterais, os programas podem ter menos bugs, serem mais fáceis de depurar e testar e serem mais adequados para verificação formal.
A programação funcional tem suas raízes na academia, evoluindo do cálculo lambda, um sistema formal de computação baseado apenas em funções. A programação funcional tem sido historicamente menos popular do que a programação imperativa, mas muitas linguagens funcionais estão sendo usadas hoje na indústria e na educação, incluindo Common Lisp, Scheme, Clojure, Wolfram Language, Racket, Erlang, Elixir, OCaml, Haskell e F#. A programação funcional também é fundamental para algumas linguagens que obtiveram sucesso em domínios específicos, como JavaScript na Web, R em estatística, J, K e Q em análise financeira e XQuery/XSLT para XML. Linguagens declarativas específicas de domínio, como SQL e Lex/Yacc, usam alguns elementos de programação funcional, como não permitir valores mutáveis. Além disso, muitas outras linguagens de programação suportam programação em um estilo funcional ou implementaram recursos de programação funcional, como C++11, C#, Kotlin, Perl, PHP, Python, Go, Rust, Raku, Scala e Java (desde Java 8).
História
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, lógica combinatória, foi desenvolvida por Moses Schönfinkel e Haskell Curry nas décadas de 1920 e 1930.
Church posteriormente desenvolveu um sistema mais fraco, o cálculo lambda de tipo simples, que estendeu o cálculo lambda atribuindo um tipo de dados a todos os termos. Isso forma a base para a programação funcional estaticamente tipada.
A primeira linguagem de programação funcional de alto nível, LISP, foi desenvolvida no final dos anos 1950 para a série IBM 700/7000 de computadores científicos 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 muitos recursos paradigmáticos da programação funcional, embora os primeiros Lisp fossem linguagens multiparadigmas 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, buscaram simplificar e racionalizar Lisp em torno de um núcleo funcional limpo, enquanto Common Lisp foi projetado para preservar e atualizar as características paradigmáticas dos numerosos dialetos mais antigos que substituiu.
A Information Processing Language (IPL), 1956, é algumas vezes citada como a primeira linguagem de programação funcional baseada em computador. É uma linguagem de estilo assembly para manipular listas de símbolos. Ele tem uma noção de gerador, que equivale a uma função que aceita uma função como um argumento e, como é uma linguagem de nível de montagem, o código pode ser dado, então o IPL pode ser considerado como tendo funções de ordem superior. No entanto, ele depende muito da estrutura de lista mutante e de recursos imperativos semelhantes.
Kenneth E. Iverson desenvolveu o APL no início dos anos 1960, descrito em seu livro de 1962 A Programming Language (ISBN 9780471430148). APL foi a principal influência no FP de John Backus. No início dos anos 1990, Iverson e Roger Hui criaram o J. Em meados dos anos 1990, Arthur Whitney, que já havia trabalhado com Iverson, criou o K, que é usado comercialmente nos setores financeiros junto com seu descendente Q.
Em meados da década de 1960, Peter Landin inventou a máquina SECD, a primeira máquina abstrata para uma linguagem de programação funcional, descreveu uma correspondência entre ALGOL 60 e o cálculo lambda e propôs a linguagem de programação ISWIM.
John Backus apresentou FP em sua palestra no Turing Award de 1977 "A programação pode ser liberada do estilo von Neumann?" Um estilo funcional e sua álgebra de programas". Ele define programas funcionais como sendo construídos de forma hierárquica por meio de "formas combinadas" que permitem uma "álgebra de programas"; na linguagem moderna, isso significa que os programas funcionais seguem o princípio da composicionalidade. O artigo de Backus popularizou a pesquisa em programação funcional, embora enfatizasse a programação em nível de função em vez do estilo lambda-calculus agora associado à programação funcional.
A linguagem ML de 1973 foi criada por Robin Milner na Universidade de Edimburgo, e David Turner desenvolveu a linguagem SASL na Universidade de St Andrews. Também em Edimburgo na década de 1970, Burstall e Darlington desenvolveram a linguagem funcional NPL. O NPL foi baseado nas Equações de Recursão de Kleene e foi introduzido pela primeira vez em seu trabalho de transformação de programas. Burstall, MacQueen e Sannella então incorporaram a verificação de tipos polimórficos de ML para produzir a linguagem Hope. ML acabou se desenvolvendo em vários dialetos, os mais comuns são agora OCaml e Standard ML.
Na década de 1970, Guy L. Steele e Gerald Jay Sussman desenvolveram o Scheme, conforme descrito nos Lambda Papers e no livro didático de 1985 Estrutura e interpretação de programas de computador. Scheme foi o primeiro dialeto de lisp a usar escopo léxico e exigir otimização de chamada final, recursos que encorajam a programação funcional.
Na década de 1980, Per Martin-Löf desenvolveu a teoria dos tipos intuicionistas (também chamada de teoria dos tipos construtiva), que associava programas funcionais com provas construtivas expressas como tipos dependentes. Isso levou a novas abordagens para a demonstração interativa de teoremas e influenciou o desenvolvimento de linguagens de programação funcionais subsequentes.
A linguagem funcional preguiçosa, Miranda, desenvolvida por David Turner, apareceu inicialmente em 1985 e teve uma forte influência em Haskell. Com Miranda sendo proprietário, Haskell começou com um consenso em 1987 para formar um padrão aberto para pesquisa de programação funcional; lançamentos de implementação estão em andamento desde 1990.
Mais recentemente, encontrou uso em nichos como CAD paramétrico na linguagem OpenSCAD construída na estrutura CGAL, embora sua restrição na reatribuição de valores (todos os valores são tratados como constantes) tenha levado a confusão entre usuários que não estão familiarizados com funções funcionais programação como um conceito.
A programação funcional continua a ser usada em ambientes comerciais.
Conceitos
Vários conceitos e paradigmas são específicos da programação funcional e geralmente estranhos à programação imperativa (incluindo a programação orientada a objetos). No entanto, as linguagens de programação geralmente atendem a vários paradigmas de programação; línguas podem ter utilizado alguns desses conceitos.
Funções de primeira classe e de ordem superior
Funções de ordem superior são funções que podem tomar outras funções como argumentos ou devolvê-las como resultados. No cálculo, um exemplo de uma função de ordem superior é o operador diferencial D/Dx- Sim., que retorna o derivado de uma função fNão..
As funções de ordem superior estão intimamente relacionadas às funções de primeira classe, pois as funções de ordem superior e as funções de primeira classe permitem funções como argumentos e resultados de outras funções. A distinção entre os dois é sutil: "ordem superior" descreve um conceito matemático de funções que operam em 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ção de uso (assim, funções de primeira classe podem aparecer em qualquer lugar no programa que outras entidades de primeira classe, como números, inclusive como argumentos para outras funções e como seus valores de retorno).
Funções de ordem superior permitem aplicação parcial ou currying, uma técnica que aplica uma função a seus argumentos um por vez, com cada aplicação retornando uma nova função que aceita o próximo argumento. Isso permite que um programador expresse sucintamente, por exemplo, a função sucessora como o operador de adição parcialmente aplicado ao número natural um.
Funções puras
Funções puras (ou expressões) não têm efeitos colaterais (memória ou E/S). Isso significa que as funções puras possuem várias propriedades úteis, muitas das quais podem ser usadas para otimizar o código:
- Se o resultado de uma expressão pura não for usado, ela pode ser removida sem afetar outras expressões.
- Se uma função pura é chamada com argumentos que não causam efeitos colaterais, o resultado é constante com relação a essa lista de argumentos (às vezes chamado transparência referencial ou idempotência), ou seja, chamando a função pura novamente com os mesmos argumentos retorna o mesmo resultado. (Isso pode permitir otimizações de cache, como a memoização.)
- Se não houver dependência de dados entre duas expressões puras, sua ordem pode ser revertida, ou elas podem ser executadas em paralelo e não podem interferir uns com os outros (em outros termos, a avaliação de qualquer expressão pura é thread-safe).
- Se toda a linguagem não permitir efeitos colaterais, então qualquer estratégia de avaliação pode ser usada; isso dá ao compilador liberdade para reordenar ou combinar a avaliação de expressões em um programa (por exemplo, usando desmatamento).
Enquanto a maioria dos compiladores para linguagens de programação imperativas detectam funções puras e executam a eliminação de subexpressões comuns para chamadas de funções puras, eles nem sempre podem fazer isso para bibliotecas pré-compiladas, que geralmente não expõem essas informações, evitando assim otimizações que envolvem essas funções. Alguns compiladores, como o gcc, adicionam palavras-chave extras para um programador marcar explicitamente funções externas como puras, para habilitar tais otimizações. O Fortran 95 também permite que as funções sejam designadas como puras. C++11 adicionou a palavra-chave constexpr
com semântica semelhante.
Recursão
A iteração (looping) em linguagens funcionais geralmente é realizada por meio de recursão. As funções recursivas invocam a si mesmas, permitindo que uma operação seja repetida até atingir o caso base. Em geral, a recursão requer a manutenção de uma pilha, que consome espaço em uma quantidade linear até a profundidade da recursão. Isso poderia tornar a recursão proibitivamente cara de usar em vez de loops imperativos. No entanto, uma forma especial de recursão conhecida como recursão de cauda pode ser reconhecida e otimizada por um compilador no mesmo código usado para implementar a iteração em linguagens imperativas. A otimização da recursão da cauda pode ser implementada transformando o programa em estilo de passagem de continuação durante a compilação, entre outras abordagens.
O padrão da linguagem Scheme requer implementações para dar suporte à recursão de cauda adequada, o que significa que eles devem permitir um número ilimitado de chamadas de cauda ativas. A recursão de cauda adequada não é simplesmente uma otimização; é um recurso de linguagem que garante aos usuários que eles podem usar a recursão para expressar um loop e, ao fazê-lo, seria seguro para o espaço. Além disso, ao contrário de seu nome, ele é responsável por todas as chamadas de cauda, não apenas pela recursão de cauda. Embora a recursão de cauda adequada geralmente seja implementada transformando o código em loops imperativos, as implementações podem implementá-la de outras maneiras. Por exemplo, Chicken intencionalmente mantém uma pilha e deixa a pilha transbordar. No entanto, quando isso acontece, seu coletor de lixo reivindicará espaço de volta, permitindo um número ilimitado de chamadas de cauda ativas, mesmo que não transforme a recursão de cauda em um loop.
Padrões comuns de recursão podem ser abstraídos usando funções de ordem superior, com catamorfismos e anamorfismos (ou "dobras" e "desdobramentos") sendo os exemplos mais óbvios. Esses esquemas de recursão desempenham um papel análogo às estruturas de controle internas, como loops em linguagens imperativas.
A maioria das linguagens de programação funcional de uso geral permitem recursão irrestrita e são Turing completas, o que torna o problema de parada indecidível, pode causar inconsistência do raciocínio equacional e geralmente requer a introdução de inconsistência na lógica expressa pelo tipo da linguagem sistema. Algumas linguagens de propósito especial, como Coq, permitem apenas recursão bem fundamentada e são fortemente normalizantes (computações sem terminação podem ser expressas apenas com fluxos infinitos de valores chamados codata). Como consequência, essas linguagens falham em ser Turing completas e expressar certas funções nelas é impossível, mas elas ainda podem expressar uma ampla classe de computações interessantes, evitando os problemas introduzidos pela recursão irrestrita. A programação funcional limitada à recursão bem fundamentada com algumas outras restrições é chamada de programação funcional total.
Avaliação rigorosa versus não rigorosa
Linguagens funcionais podem ser categorizadas se usam avaliação estrita (ansiosa) ou não estrita (preguiçosa), conceitos que se referem a como argumentos de função são processados quando um expressão está sendo avaliada. A diferença técnica está na semântica denotacional de expressões contendo cálculos falhos ou divergentes. Sob avaliação estrita, a avaliação de qualquer termo contendo um subtermo reprovado falha. Por exemplo, a expressão:
comprimento de impressão ([2+1, 3*2, 1/0, 5-4])
falha na avaliação estrita devido à divisão por zero no terceiro elemento da lista. Na avaliação preguiçosa, a função length retorna o valor 4 (ou seja, o número de itens da lista), pois avaliá-la não tenta avaliar os termos que compõem a lista. Em resumo, a avaliação estrita sempre avalia totalmente os argumentos da função antes de invocar a função. A avaliação preguiçosa não avalia argumentos de função, a menos que seus valores sejam necessários para avaliar a própria chamada de função.
A estratégia de implementação usual para avaliação preguiçosa em linguagens funcionais é a redução de grafos. A avaliação preguiçosa é usada por padrão em várias linguagens funcionais puras, incluindo Miranda, Clean e Haskell.
Hughes 1984 defende a avaliação preguiçosa como um mecanismo para melhorar a modularidade do programa por meio da separação de preocupações, facilitando a implementação independente de produtores e consumidores de fluxos de dados. Launchbury 1993 descreve algumas dificuldades que a avaliação preguiçosa apresenta, particularmente na análise dos requisitos de armazenamento de um programa, e propõe uma semântica operacional para auxiliar em tal análise. Harper 2009 propõe incluir avaliações estrita e preguiçosa na mesma linguagem, usando o sistema de tipos da linguagem para distingui-las.
Sistemas de tipo
Especialmente desde o desenvolvimento da inferência de tipos de Hindley-Milner na década de 1970, as linguagens de programação funcional tendem a usar cálculo lambda tipado, rejeitando todos os programas inválidos no tempo de compilação e arriscando erros falsos positivos, 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 falsos negativos, usados em Lisp e suas variantes (como Scheme), pois 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 conveniente a manipulação de estruturas de dados complexas; a presença de forte verificação de tipo em tempo de compilação torna os programas mais confiáveis na ausência de outras técnicas de confiabilidade, como desenvolvimento orientado a testes, enquanto a inferência de tipo libera o programador da necessidade de declarar tipos manualmente ao compilador na maioria dos casos.
Algumas linguagens funcionais orientadas para pesquisa, como Coq, Agda, Cayenne e Epigram, são baseadas na teoria de tipo intuicionista, que permite que os tipos dependam de termos. Esses tipos são chamados de tipos dependentes. Esses sistemas de tipo não têm inferência de tipo decidível e são difíceis de entender e programar. Mas os tipos dependentes podem expressar proposições arbitrárias na lógica de ordem superior. Por meio do isomorfismo de Curry-Howard, então, programas bem tipados nessas linguagens tornam-se um meio de escrever provas matemáticas formais a partir das quais um compilador pode gerar código certificado. Embora essas linguagens sejam de interesse principalmente na pesquisa acadêmica (inclusive na matemática formalizada), elas também começaram a ser usadas na engenharia. Compcert é um compilador para um subconjunto da linguagem de programação C que é escrito em Coq e verificado formalmente.
Uma forma limitada de tipos dependentes chamados tipos de dados algébricos generalizados (GADT's) pode ser implementada de forma a fornecer alguns dos benefícios da programação de tipo dependente, evitando a maior parte de sua inconveniência. Os GADT's estão disponíveis no Glasgow Haskell Compiler, no OCaml e no Scala, e foram propostos como adições a outras linguagens, incluindo Java e C#.
Transparência referencial
Os programas funcionais não possuem instruções de atribuição, ou seja, o valor de uma variável em um programa funcional nunca muda depois de definido. Isso elimina qualquer chance de efeitos colaterais porque qualquer variável pode ser substituída por seu valor real em qualquer ponto da execução. Assim, os programas funcionais são referencialmente transparentes.
Considere a instrução de atribuição C x=x * 10
, isso altera 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
resultam em 10
e 100
respectivamente. Claramente, substituir x=x * 10
por 10
ou 100
dá ao programa um significado diferente e, portanto, a expressão não é referencialmente transparente. Na verdade, as instruções de atribuição nunca são referencialmente transparentes.
Agora, considere outra função como int mais um(int x) {retorno x+1;}
é transparente, pois não altera implicitamente a entrada x e, portanto, não tem tais efeitos colaterais.
Os programas funcionais usam exclusivamente esse tipo de função e, portanto, são referencialmente transparentes.
Estruturas de dados
Estruturas de dados puramente funcionais geralmente são representadas de maneira diferente de suas contrapartes imperativas. Por exemplo, o 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 hash e o heap binário, são baseadas em arrays. Arrays podem ser substituídos por mapas ou listas de acesso aleatório, que admitem implementação puramente funcional, mas possuem tempos logarítmicos de acesso e atualização. 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 para suas contrapartes imperativas. Vetores persistentes, por exemplo, usam árvores para atualização parcial. Chamar o método insert resultará na criação de alguns, mas não de todos os nós.
Comparação com a programação imperativa
A programação funcional é muito diferente da programação imperativa. As diferenças mais significativas decorrem do fato de que a programação funcional evita efeitos colaterais, que são usados na programação imperativa para implementar estado e E/S. A programação funcional pura evita completamente os efeitos colaterais e fornece transparência referencial.
As funções de ordem superior raramente são usadas na programação imperativa mais antiga. Um programa imperativo tradicional pode usar um loop para percorrer e modificar uma lista. Um programa funcional, por outro lado, provavelmente usaria uma função de “mapa” de ordem superior que recebe uma função e uma lista, gerando e retornando uma nova lista aplicando a função a cada item da lista.
Programação imperativa x programação funcional
Os dois exemplos a seguir (escritos em JavaScript) alcançam o mesmo efeito: eles multiplicam todos os números pares em uma matriz por 10 e os somam, armazenando a soma final na variável "resultado".
Circuito Imperativo Tradicional:
Não. Não. Lista = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = Não.1, 2, 3, 4, 5, 6, 7, 8, 9, 10.]Deixa-me. resultado = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 0;para (Deixa-me. Eu... = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 0; Eu... < Não. Lista.comprimento; Eu...++) ( se (Não. ListaNão.Eu...] % 2 - Sim. 0) ( resultado - Sim. Não. ListaNão.Eu...] * 10.; ??
Programação funcional com funções de ordem superior:
Não. resultado = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = Não.1, 2, 3, 4, 5, 6, 7, 8, 9, 10.] .filtro de filtro(n - Sim. n % 2 - Sim. 0) .mapa(um - Sim. um * 10.) .reduzir(um, b)) - Sim. um + b));
Simulando estado
Existem tarefas (por exemplo, manter o saldo de uma conta bancária) que muitas vezes parecem mais naturalmente implementadas com o estado. A programação funcional pura realiza essas tarefas e tarefas de E/S, como aceitar a entrada do usuário e imprimir na tela, de uma maneira diferente.
A linguagem de programação funcional pura Haskell os implementa usando mônadas, derivadas da teoria das categorias. As mônadas oferecem uma maneira de abstrair certos tipos de padrões computacionais, incluindo (mas não limitado a) modelagem de computações com estado mutável (e outros efeitos colaterais, como E/S) de maneira imperativa sem perder a pureza. Embora as mônadas existentes possam ser fáceis de aplicar em um programa, dados modelos e exemplos apropriados, muitos alunos acham difícil entendê-las conceitualmente, por exemplo, quando solicitados a definir novas mônadas (o que às vezes é necessário para certos tipos de bibliotecas).
As linguagens funcionais também simulam estados passando estados imutáveis. Isso pode ser feito fazendo com que uma função aceite o estado como um de seus parâmetros e retorne um novo estado junto com o resultado, deixando o antigo estado inalterado.
Linguagens funcionais impuras geralmente incluem um método mais direto de gerenciamento de estado mutável. Clojure, por exemplo, usa referências gerenciadas que podem ser atualizadas aplicando funções puras ao estado atual. Esse tipo de abordagem permite a mutabilidade enquanto ainda promove o uso de funções puras como a forma preferida de expressar computações.
Métodos alternativos, como a lógica e a exclusividade de Hoare, foram desenvolvidos para rastrear efeitos colaterais em programas. Algumas linguagens de pesquisa modernas usam sistemas de efeitos para tornar explícita a presença de efeitos colaterais.
Problemas de eficiência
Linguagens de programação funcional são normalmente menos eficientes no uso de CPU e memória do que linguagens imperativas como C e Pascal. Isso está relacionado ao fato de que algumas estruturas de dados mutáveis, como arrays, têm uma implementação muito direta usando o hardware atual. As matrizes planas podem ser acessadas de maneira muito eficiente com CPUs com pipelines profundos, pré-buscadas com eficiência por meio de caches (sem busca complexa de ponteiros) ou manipuladas com instruções SIMD. Também não é fácil criar suas contrapartes imutáveis de propósito geral igualmente eficientes. Para linguagens puramente funcionais, a desaceleração do 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, essas desacelerações não são universais. Para programas que executam cálculos numéricos intensivos, linguagens funcionais como OCaml e Clean são apenas um pouco mais lentas que 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 matriz (como J e K) foram projetadas com otimizações de velocidade.
A imutabilidade dos dados pode, em muitos casos, levar à eficiência de execução, permitindo que o compilador faça suposições inseguras em uma linguagem imperativa, aumentando assim as oportunidades de expansão em linha.
A avaliação preguiçosa também pode acelerar o programa, mesmo assintoticamente, enquanto pode desacelerá-lo no máximo por um fator constante (no entanto, pode introduzir vazamentos de memória se usado incorretamente). Launchbury 1993 discute questões teóricas relacionadas a vazamentos de memória da avaliação preguiçosa, e O'Sullivan et al. 2008 fornece alguns conselhos práticos para analisá-los e corrigi-los. No entanto, as implementações mais gerais de avaliação preguiçosa que fazem uso extensivo de código e dados desreferenciados têm desempenho ruim em processadores modernos com pipelines profundos e caches de vários níveis (onde uma falta de cache pode custar centenas de ciclos).
Programação funcional em linguagens não funcionais
É possível usar um estilo funcional de programação em linguagens que não são tradicionalmente consideradas linguagens funcionais. Por exemplo, D e Fortran 95 explicitamente suportam funções puras.
JavaScript, Lua, Python e Go tiveram funções de primeira classe desde o início. Python tinha suporte para "lambda", "mapear", "reduzir" e "filtro" em 1994, bem como encerramentos no Python 2.2, embora o Python 3 tenha relegado "reduzir" para o módulo de biblioteca padrão functools
. Funções de primeira classe foram introduzidas em outras linguagens convencionais, como PHP 5.3, Visual Basic 9, C# 3.0, C++11 e Kotlin.
No PHP, classes anônimas, encerramentos e lambdas são totalmente suportados. Bibliotecas e extensões de linguagem para estruturas de dados imutáveis estão sendo desenvolvidas para auxiliar a programação no estilo funcional.
Em Java, classes anônimas às vezes podem ser usadas para simular encerramentos; no entanto, as classes anônimas nem sempre são substituições adequadas para encerramentos porque têm recursos mais limitados. Java 8 suporta expressões lambda como um substituto para algumas classes anônimas.
Em C#, as classes anônimas não são necessárias, porque os encerramentos e lambdas são totalmente suportados. Bibliotecas e extensões de linguagem para estruturas de dados imutáveis estão sendo desenvolvidas para auxiliar a programação no estilo funcional em C#.
Muitos padrões de projeto orientados a objetos podem ser expressos em termos de programação funcional: por exemplo, o padrão de estratégia simplesmente determina o uso de uma função de ordem superior, e o padrão de visitante corresponde aproximadamente a um catamorfismo ou dobra.
Da mesma forma, a ideia de dados imutáveis da programação funcional é frequentemente incluída em linguagens de programação imperativas, por exemplo, a tupla em Python, que é um array imutável, e Object.freeze() em JavaScript.
Aplicativos
Planilhas
As planilhas podem ser consideradas uma forma de sistema de programação funcional puro, de ordem zero e de avaliação estrita. No entanto, planilhas geralmente carecem de funções de ordem superior, bem como reutilização de código e, em algumas implementações, também carecem de recursão. Várias extensões foram desenvolvidas para programas de planilhas para permitir funções de ordem superior e reutilizáveis, mas até agora permanecem principalmente de natureza acadêmica.
Academia
A programação funcional é uma área ativa de pesquisa no campo da teoria da linguagem de programação. Existem vários locais de publicação revisados por pares com foco em programação funcional, incluindo a Conferência Internacional de Programação Funcional, o Journal of Functional Programming e o Symposium on Trends in Functional Programming.
Indústria
A programação funcional tem sido empregada em uma ampla gama de aplicações industriais. Por exemplo, o Erlang, que foi desenvolvido pela empresa sueca Ericsson no final dos anos 1980, foi originalmente usado para implementar sistemas de telecomunicações tolerantes a falhas, mas desde então se tornou popular para a construção de uma gama de aplicações em empresas como Nortel, Facebook, Électricité de França e WhatsApp. Scheme, um dialeto de Lisp, foi usado como base para vários aplicativos nos primeiros computadores Apple Macintosh e foi aplicado a problemas como software de simulação de treinamento e controle de telescópio. OCaml, que foi introduzido em meados da década de 1990, teve uso comercial em áreas como análise financeira, verificação de driver, programação de robôs industriais e análise estática de software embarcado. Haskell, embora inicialmente concebido como uma linguagem de pesquisa, também foi aplicado em áreas como sistemas aeroespaciais, design de hardware e programação web.
Outras linguagens de programação funcional que foram usadas na indústria incluem Scala, F#, Wolfram Language, Lisp, Standard ML e Clojure.
As "plataformas" têm sido populares em finanças para análise de risco (particularmente com grandes bancos de investimento). Os fatores de risco são codificados como funções que formam gráficos (categorias) interdependentes para medir as correlações nas mudanças do mercado, de maneira semelhante às otimizações de base de Gröbner, mas também para estruturas regulatórias, como análise e revisão abrangentes de capital. Dado o uso de variações OCaml e Caml em finanças, esses sistemas às vezes são considerados relacionados a uma máquina abstrata categórica. A programação funcional é fortemente influenciada pela teoria das categorias.
Educação
Muitas universidades ensinam programação funcional. Alguns o tratam como um conceito introdutório de programação, enquanto outros primeiro ensinam métodos de programação imperativos.
Fora da ciência da computação, a programação funcional é usada para ensinar a resolução de problemas, conceitos algébricos e geométricos. Também tem sido usado para ensinar mecânica clássica, como no livro Estrutura e Interpretação da Mecânica Clássica.
Notas e referências
Contenido relacionado
Botão vermelho BBC
Ser
Dragão 32/64