Programação funcional

Lambda, símbolo comumente usado para denotar abstrações no cálculo lambda.

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 (que enfatiza mudanças no estado do programa).[1] Enfatizando as expressões ao invés de comandos, as expressões são utilizados para cálculo de valores com dados imutáveis.[2]

Uma função, neste sentido, pode ter ou não ter parâmetros (valores de entrada da função) e um simples valor de retorno (resultado da função). A definição de uma função descreve como a função será avaliada em termos de outras funções. Por exemplo, a função é definida em termos de funções de exponenciação e adição. Do mesmo modo, a linguagem deve oferecer funções básicas que não requerem definições adicionais.

A programação funcional trata as funções de forma em que estas possam ser passadas como parâmetro e valores para outras e funções e podendo ter o resultado armazenado em uma constante.[3]

Linguagens de programação funcionais, especialmente as puramente funcionais, têm sido mais usadas academicamente que no desenvolvimento comercial de software. Entretanto, algumas linguagens notáveis usadas na indústria e no comércio incluem Erlang (aplicações concorrentes),[4] R (estatística), OCaml (quantitative trading)[5], Mathematica (matemática simbólica)[6] J, K (análise financeira) e XSLT.[7][8] Importantes influências na programação funcional foram o cálculo lambda, as linguagens de programação APL e Lisp, e mais recentemente ML, Haskell, OCaml, F# e Elixir.

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,[9] 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.[10]

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.[11] 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).[12] 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.[13] 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.[14]

A Linguagem de Processamento de Informação (IPL), de 1956, é por vezes citada como a primeira linguagem de programação funcional baseada em computador.[15] É uma linguagem de estilo assembly para manipular listas de símbolos. Possui uma noção de gerador, que equivale a uma função que aceita uma função como argumento e, como é uma linguagem de baixo nível, o código pode ser visto como dados, pelo que a IPL pode ser considerada como tendo funções de ordem superior. No entanto, depende fortemente da estrutura de lista mutável e de características imperativas semelhantes.

Kenneth E. Iverson desenvolveu a linguagem APL no início da década de 1960, descrita no seu livro de 1962 A Programming Language (ISBN 9780471430148). A APL foi a principal influência na linguagem FP de John Backus. No início da década de 1990, Iverson e Roger Hui criaram a linguagem J. Em meados da década de 1990, Arthur Whitney, que já havia trabalhado com Iverson, criou a linguagem K, que é usada comercialmente nas indústrias financeiras juntamente com a sua descendente Q.

Em meados da década de 1960, Peter Landin inventou a máquina SECD,[16] a primeira máquina abstrata para uma linguagem de programação funcional,[17] descreveu uma correspondência entre o ALGOL 60 e o cálculo lambda,[18][19] e propôs a linguagem de programação ISWIM.[20]

John Backus apresentou a linguagem FP na sua palestra do Prêmio Turing de 1977 "Can Programming Be Liberated From the von Neumann Style? A Functional Style and its Algebra of Programs".[21] Ele define os programas funcionais como sendo construídos de forma hierárquica por meio de "formas de combinação" que permitem uma "álgebra de programas"; na linguagem moderna, isto 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 ao nível da função em vez do estilo de cálculo lambda hoje 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.[22] A NPL baseava-se nas Equações de Recursão de Kleene (ver Teorema da recursão de Kleene) e foi introduzida pela primeira vez no seu trabalho sobre transformação de programas.[23] Burstall, MacQueen e Sannella incorporaram depois a verificação de tipos polimórfica do ML para produzir a linguagem Hope.[24] O ML acabou por se desenvolver em vários dialetos, dos quais os mais comuns são agora o OCaml e o Standard ML.

Na década de 1970, Guy L. Steele e Gerald Jay Sussman desenvolveram a linguagem Scheme, conforme descrito nos Lambda Papers e no livro de 1985 Structure and Interpretation of Computer Programs. O Scheme foi o primeiro dialeto de Lisp a usar o escopo léxico e a exigir a otimização de chamadas de cauda, características que encorajam a programação funcional.

Na década de 1980, Per Martin-Löf desenvolveu a teoria dos tipos intuicionista (também chamada de teoria dos tipos construtiva), que associava programas funcionais a provas construtivas expressas como tipos dependentes. Isto levou a novas abordagens à demonstração interativa de teoremas e influenciou o desenvolvimento de linguagens de programação funcional subsequentes.

A linguagem funcional preguiçosa (lazy), Miranda, desenvolvida por David Turner, apareceu inicialmente em 1985 e teve uma forte influência sobre a linguagem Haskell. Sendo o Miranda proprietário, o Haskell começou com um consenso em 1987 para formar um padrão aberto para a investigação em programação funcional; os lançamentos de implementações têm ocorrido desde 1990.

Mais recentemente, tem encontrado uso em nichos como o CAD paramétrico na linguagem OpenSCAD, construída sobre o framework CGAL, embora a sua restrição à reatribuição de valores (todos os valores são tratados como constantes) tenha gerado confusão entre os utilizadores que não estão familiarizados com a programação funcional como conceito.[25]

A programação funcional continua a ser utilizada em ambientes comerciais.[26][27][28]

Conceitos

Uma série de conceitos[29] 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.[30]

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 , que retorna a derivada de uma função .

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).

As funções de ordem superior permitem a aplicação parcial ou o currying, uma técnica que aplica uma função aos seus argumentos um de cada vez, com cada aplicação retornando uma nova função que aceita o argumento seguinte. Isto permite que um programador expresse de forma sucinta, por exemplo, a função sucessora como o operador de adição aplicado parcialmente ao número natural um.

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:

  • Se o resultado de uma expressão pura não for usado, ele pode ser removido sem afetar outras expressões.
  • Se uma função pura for chamada com argumentos que não causam efeitos colaterais, o resultado é constante em relação a essa lista de argumentos (por vezes chamado de transparência referencial ou idempotência), ou seja, chamar a função pura novamente com os mesmos argumentos retorna o mesmo resultado. (Isto pode permitir otimizações de cache, como a memoização.)
  • Se não houver dependência de dados entre duas expressões puras, a sua ordem pode ser revertida, ou podem ser realizadas em paralelo e não podem interferir uma com a outra (noutros termos, a avaliação de qualquer expressão pura é segura para threads/thread-safe).
  • Se a linguagem no seu todo não permitir efeitos colaterais, então qualquer estratégia de avaliação pode ser usada; isto dá ao compilador a liberdade para reordenar ou combinar a avaliação de expressões num programa (por exemplo, usando a desflorestação/deforestation).

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).[31] 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.

O padrão da linguagem Scheme requer que as implementações suportem a recursividade de cauda adequada, o que significa que devem permitir um número ilimitado de chamadas de cauda ativas.[32][33] A recursividade de cauda adequada não é simplesmente uma otimização; é um recurso da linguagem que garante aos utilizadores que podem usar a recursividade para expressar um laço e fazê-lo seria seguro em termos de espaço.[34] Além disso, ao contrário do seu nome, engloba todas as chamadas de cauda, e não apenas a recursividade de cauda. Embora a recursividade de cauda adequada seja geralmente implementada transformando o código em laços imperativos, as implementações podem fazê-lo de outras formas. Por exemplo, o Chicken mantém intencionalmente uma pilha e permite que a pilha transborde. No entanto, quando isto acontece, o seu coletor de lixo reclamará o espaço de volta,[35] permitindo um número ilimitado de chamadas de cauda ativas mesmo não transformando a recursividade de cauda num laço.

Padrões comuns de recursividade podem ser abstraídos usando funções de ordem superior, sendo os catamorfismos e os anamorfismos (ou "dobras" e "desdobramentos" / folds e unfolds) os exemplos mais óbvios. Esses esquemas de recursividade desempenham um papel análogo às estruturas de controle integradas, como laços em linguagens imperativas.

A maioria das linguagens de programação funcional de uso geral permite a recursividade irrestrita e são Turing completas, o que torna o problema da parada um problema indecidível, pode causar inconsistência no raciocínio equacional, e geralmente requer a introdução de inconsistência na lógica expressa pelo sistema de tipos da linguagem. Algumas linguagens de propósito especial, como Rocq, permitem apenas recursividade bem fundada e são fortemente normalizáveis (computações que não terminam podem ser expressas apenas com fluxos infinitos de valores chamados codados). Como consequência, estas linguagens falham em ser Turing completas e expressar certas funções nelas é impossível, mas ainda podem expressar uma vasta classe de computações interessantes, evitando os problemas introduzidos pela recursividade irrestrita. A programação funcional limitada à recursividade bem fundada com algumas outras restrições é chamada de programação funcional total.[36]

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:

print(len([2 + 1, 3 * 2, 1 / 0, 5 - 4]))

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.

A estratégia de implementação habitual para a avaliação preguiçosa em linguagens funcionais é a redução de grafos.[37] A avaliação preguiçosa é usada por defeito 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 através da separação de interesses/conceitos, facilitando a implementação independente de produtores e consumidores de fluxos de dados.[38] Launchbury 1993 descreve algumas dificuldades que a avaliação preguiçosa introduz, particularmente na análise dos requisitos de armazenamento de um programa, e propõe uma semântica operacional para auxiliar em tal análise.[39] Harper 2009 propõe incluir tanto a avaliação estrita como a preguiçosa na mesma linguagem, usando o sistema de tipos da linguagem para distingui-las.[40]

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.

Algumas linguagens funcionais orientadas para a pesquisa, como Rocq, Agda, Cayenne e Epigram, baseiam-se na teoria dos tipos intuicionista, que permite que os tipos dependam de termos. Tais tipos são chamados de tipos dependentes. Estes sistemas de tipos não têm inferência de tipos decidível e são difíceis de compreender e programar com eles.[41][42][43][44] Mas os tipos dependentes podem expressar proposições arbitrárias na lógica de ordem superior. Através do Isomorfismo de Curry-Howard, os programas bem tipados nestas linguagens tornam-se um meio de escrever provas matemáticas formais a partir das quais um compilador pode gerar código certificado. Embora estas linguagens sejam principalmente de interesse na investigação acadêmica (incluindo na matemática formalizada), elas começaram a ser usadas na engenharia também. O CompCert é um compilador para um subconjunto da linguagem C que é escrito em Rocq e verificado formalmente.[45]

Uma forma limitada de tipos dependentes chamados tipos de dados algébricos generalizados (GADTs) pode ser implementada de forma a fornecer alguns dos benefícios da programação tipada de forma dependente, evitando a maior parte de seus inconvenientes.[46] Os GADTs estão disponíveis no Glasgow Haskell Compiler, em OCaml[47] e em Scala,[48] e têm sido propostos como adições a outras linguagens, incluindo Java e C#.[49]

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.[50]

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.

Agora, considere que outra função como int plusOne(int x) { return x + 1; } é transparente, pois não muda implicitamente a entrada x e, portanto, não tem tais efeitos colaterais.Os programas funcionais usam exclusivamente este tipo de função e são, portanto, referencialmente transparentes.

Estruturas de dados

As estruturas de dados puramente funcionais são frequentemente representadas de uma forma diferente das suas contrapartes imperativas.[51] 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.[52]

Comparação com a programação imperativa

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.

Laço imperativo tradicional:

int[] numList = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int result = 0;
for (int i: numList) {
    if (i % 2 == 0) {
        result += i * 10;
    }
}

Programação funcional com funções de ordem superior:

import java.util.Arrays;

int[] numList = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int result = Arrays.stream(numList)
    .filter(n -> n % 2 == 0)
    .map(n -> n * 10)
    .reduce(0, Integer::sum);

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.[53] 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).[54]

As linguagens funcionais também simulam estados passando estados imutáveis de um lado para o outro. Isso pode ser feito fazendo com que uma função aceite o estado como um de seus parâmetros, e retorne um novo estado juntamente com o resultado, deixando o estado antigo inalterado.[55] As linguagens funcionais impuras geralmente incluem um método mais direto de gerir o estado mutável. O Clojure, por exemplo, usa referências geridas que podem ser atualizadas aplicando funções puras ao estado atual. Este tipo de abordagem permite a mutabilidade enquanto promove o uso de funções puras como a forma preferencial de expressar cálculos. Métodos alternativos como a Lógica de Hoare e a unicidade foram desenvolvidos para rastrear efeitos colaterais em programas. Algumas linguagens de investigação modernas usam sistemas de efeitos para tornar explícita a presença de efeitos colaterais.[56]

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.[57] 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.[58] 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.

A imutabilidade dos dados pode, em muitos casos, levar à eficiência de execução ao permitir que o compilador faça suposições que são inseguras numa linguagem imperativa, aumentando assim as oportunidades para expansão em linha (inline expansion).[59] Mesmo que a cópia envolvida, que pode parecer implícita ao lidar com estruturas de dados imutáveis persistentes, possa parecer computacionalmente dispendiosa, algumas linguagens de programação funcional, como o Clojure, resolvem esta questão implementando mecanismos para o compartilhamento seguro de memória entre dados formalmente imutáveis.[60] O Rust distingue-se pela sua abordagem à imutabilidade de dados que envolve referências imutáveis[61] e um conceito chamado tempos de vida (lifetimes).[62]

Dados imutáveis com separação de identidade e estado e esquemas shared-nothing (compartilhamento nulo) também podem ser potencialmente mais adequados para a programação concorrente e paralela devido à redução ou eliminação do risco de certos perigos de concorrência, uma vez que as operações concorrentes são geralmente atômicas e isto permite eliminar a necessidade de bloqueios (locks). É assim, por exemplo, que as classes java.util.concurrent são implementadas, onde algumas delas são variantes imutáveis das classes correspondentes que não são adequadas para uso concorrente.[63] As linguagens de programação funcional muitas vezes têm um modelo de concorrência que, em vez de estado compartilhado e sincronização, aproveita mecanismos de passagem de mensagens (como o modelo de atores, onde cada ator é um contentor para estado, comportamento, atores filhos e uma fila de mensagens).[64][65] Esta abordagem é comum em Erlang/Elixir ou Akka.

A avaliação preguiçosa também pode acelerar o programa, mesmo de forma assintótica, enquanto pode abrandá-lo no máximo por um fator constante (no entanto, pode introduzir vazamentos de memória se for utilizada indevidamente). Launchbury 1993[39] discute questões teóricas relacionadas com vazamentos de memória a partir da avaliação preguiçosa, e O'Sullivan et al. 2008[66] dão alguns conselhos práticos para analisá-los e corrigi-los. Contudo, as implementações mais gerais de avaliação preguiçosa, fazendo uso extensivo de código e dados desreferenciados, têm um desempenho fraco nos processadores modernos com pipelines profundos e caches multinível (onde uma falha de cache pode custar centenas de ciclos).[67]

Custo da abstração

Algumas linguagens de programação funcional podem não otimizar abstrações, como funções de ordem superior como "map" ou "filter", de forma tão eficiente quanto as operações imperativas subjacentes. Considere, como exemplo, as duas formas seguintes de verificar se 5 é um número par em Clojure:

(even? 5)
(.equals (mod 5 2) 0)

Quando sujeito a testes de desempenho (benchmark) utilizando a ferramenta Criterium num PC GNU/Linux com Ryzen 7900X num REPL 2.11.2 do Leiningen, executado sobre a versão 22 da Java VM e a versão 1.11.1 do Clojure, a primeira implementação, que está implementada como:

(defn even?
  "Returns true if n is even, throws an exception if n is not an integer"
  {:added "1.0"
   :static true}
   [n] (if (integer? n)
        (zero? (bit-and (clojure.lang.RT/uncheckedLongCast n) 1))
        (throw (IllegalArgumentException. (str "Argument must be an integer: " n)))))

tem um tempo de execução médio de 4,76 ms, enquanto a segunda, na qual .equals é uma invocação direta do método Java subjacente, tem um tempo de execução médio de 2,8 μs – aproximadamente 1700 vezes mais rápido. Parte disso pode ser atribuído à verificação de tipos e tratamento de exceções envolvidos na implementação de even?. Por exemplo, a biblioteca lo para Go, que implementa várias funções de ordem superior comuns em linguagens de programação funcional usando genéricos. Num teste de desempenho fornecido pelo autor da biblioteca, chamar map é 4% mais lento que um laço for equivalente e tem o mesmo perfil de alocação,[68] o que pode ser atribuído a várias otimizações do compilador, como o inlining.[69]

Uma caraterística distintiva do Rust são as abstrações de custo zero. Isto significa que usá-las não impõe nenhum custo adicional (overhead) em tempo de execução. Isso é conseguido graças ao compilador usar o desenrolamento de laços (loop unrolling), onde cada iteração de um laço, seja ele imperativo ou usando iteradores, é convertido numa instrução Assembly independente, sem o custo do código de controlo do laço. Se uma operação iterativa escreve num arranjo, os elementos do arranjo resultante serão armazenados em registos específicos da CPU, permitindo o acesso em tempo constante durante a execução.[70]

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.[71] Por exemplo, tanto o D[72] quanto o Fortran 95[31] suportam explicitamente funções puras.

O JavaScript, Lua,[73] Python e Go[74] tiveram funções de primeira classe desde o seu início.[75] O Python tinha suporte para "lambda", "map", "reduce" e "filter" em 1994, bem como fechamentos (closures) no Python 2.2,[76] embora o Python 3 tenha relegado o "reduce" para o módulo functools da biblioteca padrão.[77] 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.[3]

Em Perl, a lambda, map, reduce, filter e os fechamentos (closures) são totalmente suportados e frequentemente usados. O livro Higher-Order Perl, lançado em 2005, foi escrito para fornecer um guia abrangente sobre o uso do Perl para programação funcional.

Em PHP, as classes anônimas, os fechamentos e os lambdas são totalmente suportados. Bibliotecas e extensões de linguagem para estruturas de dados imutáveis estão a ser desenvolvidas para auxiliar a programação no estilo funcional.

Em Java, as classes anônimas podem por vezes ser usadas para simular fechamentos;[78] no entanto, as classes anônimas nem sempre são substitutas adequadas para os fechamentos porque têm capacidades mais limitadas.[79] O Java 8 suporta expressões lambda como substituto de algumas classes anônimas.[80]

Em C#, as classes anônimas não são necessárias, porque os fechamentos e os lambdas são totalmente suportados. Bibliotecas e extensões de linguagem para estruturas de dados imutáveis estão a ser desenvolvidas para auxiliar a programação no estilo funcional em C#.

Muitos padrões de projeto orientados a objetos são expressáveis em termos de programação funcional: por exemplo, o padrão strategy dita simplesmente o uso de uma função de ordem superior, e o padrão visitor corresponde vagamente a um catamorfismo, ou fold.

Da mesma forma, a ideia de dados imutáveis da programação funcional é frequentemente incluída nas linguagens de programação imperativas,[81] por exemplo o tuple (tupla) no Python, que é um arranjo imutável, e o Object.freeze() no JavaScript.[82]

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

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.[83] 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:

mother(charles, elizabeth).
mother(harry, diana).

O programa pode ser consultado, tal como um programa funcional, para gerar mães a partir dos filhos:

?- mother(harry, X).
X = diana.
?- mother(charles, X).
X = elizabeth.

Mas também pode ser consultado de trás para a frente, para gerar filhos:

?- mother(X, elizabeth).
X = charles.
?- mother(X, diana).
X = harry.

Pode até mesmo ser utilizado para gerar todas as instâncias da relação de mãe:

?- mother(X, Y).
X = charles,
Y = elizabeth.
X = harry,
Y = diana.

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:

maternal_grandmother(X) = mother(mother(X)).

A mesma definição em notação relacional precisa de ser escrita na forma não aninhada:

maternal_grandmother(X, Y) :- mother(X, Z), mother(Z, Y).

Aqui :- significa se e , significa e.

No entanto, a diferença entre as duas representações é simplesmente sintática. No Prolog Ciao, as relações podem ser aninhadas, como as funções na programação funcional:[84]

grandparent(X) := parent(parent(X)).
parent(X) := mother(X).
parent(X) := father(X).

mother(charles) := elizabeth.
father(charles) := phillip.
mother(harry) := diana.
father(harry) := charles.

?- grandparent(X,Y).
X = harry,
Y = elizabeth.
X = harry,
Y = phillip.

O Ciao transforma a notação semelhante à de funções para a forma relacional e executa o programa em lógica resultante utilizando a estratégia de execução padrão do Prolog.

Aplicações

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.[85]

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).[86] 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.[87]

Microsserviços

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

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.[89][90] 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.

Outras linguagens de programação funcional que têm visto uso na indústria incluem o Scala,[91] F#, a Wolfram Language, o Lisp,[92] o Standard ML[93][94] e o Clojure.[95] O Scala tem sido amplamente utilizado em Ciência de dados,[96] enquanto o ClojureScript,[97] Elm[98] ou PureScript[99] são algumas das linguagens de programação de front-end funcionais utilizadas em produção. A estrutura de software Phoenix do Elixir também é usada por alguns projetos comerciais relativamente populares, como o Font Awesome ou o Allegro Lokalnie, a plataforma de anúncios classificados da Allegro (uma das maiores plataformas de comércio eletrónico da Polónia).[100][101]

As "plataformas" funcionais têm sido populares em finanças para análise de risco (particularmente em grandes bancos de investimento). Os fatores de risco são codificados como funções que formam grafos (categorias) interdependentes para medir correlações em mudanças de mercado, semelhante a otimizações de base de Gröbner, mas também para estruturas regulatórias. Dado o uso de OCaml e variações de Caml em finanças, estes sistemas são por vezes considerados relacionados com uma máquina abstrata categórica. A programação funcional é fortemente influenciada pela teoria das categorias.

Educação

Muitas universidades ensinam programação funcional.[102][103][104][105] Algumas tratam-na como um conceito introdutório à programação,[105] enquanto outras ensinam primeiro métodos de programação imperativa.[104][106]

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.[107] 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.[108][109]

Ver também

Referências

  1. Paul Hudak (setembro de 1989). "Conception, evolution, and application of functional programming languages" (pdf) . ACM Computing Surveys.
  2. R. Gudwin, Ricardo. «Linguagens de Programação» (PDF). Universidade Estadual de Campinas Faculdade de Engenharia Elétrica e de Computação
  3. 1 2 Jungthon, Machado Goulart, Gustavo, Cristian. «Paradigmas de Programação» (PDF). Faculdade de Informática de Taquara (FIT)
  4. «Who uses Erlang for product development?» (em inglês). Consultado em 27 de junho de 2006
  5. «Technology :: Jane Street». www.janestreet.com (em inglês). Consultado em 25 de setembro de 2023
  6. Departamento de Matemática Aplicada, Universidade do Colorado. «Functional vs. Procedural Programming Language» (em inglês). Consultado em 28 de agosto de 2006. Arquivado do original em 13 de novembro de 2007
  7. Dimitre Novatchev. «The Functional Programming Language XSLT - A proof through examples» (em inglês). Consultado em 27 de maio de 2006
  8. David Mertz. «XML Programming Paradigms (part four): Functional Programming approached to XML processing» (em inglês). Consultado em 27 de maio de 2006
  9. Turing, A. M. (1937). «Computability and λ-definability». Cambridge University Press. The Journal of Symbolic Logic. 2 (4): 153–163. doi:10.2307/2268280
  10. Curry, Haskell Brooks; Feys, Robert (1958). Combinatory Logic. [S.l.]: North-Holland Publishing Company. Consultado em 10 de fevereiro de 2013
  11. Church, A. (1940). «A Formulation of the Simple Theory of Types». Journal of Symbolic Logic. 5 (2): 56–68. doi:10.2307/2266170
  12. McCarthy, John (junho de 1978). «History of LISP». The first ACM SIGPLAN conference on History of programming languages - HOPL-1 (PDF). Los Angeles, CA: [s.n.] pp. 173–185. doi:10.1145/800025.808387
  13. McCarthy, John (1960). «Recursive functions of symbolic expressions and their computation by machine, Part I.» (PDF). Communications of the ACM. 3 (4): 184–195. doi:10.1145/367177.367199
  14. Steele, Guy L.; Gabriel, Richard P. (fevereiro de 1996). «The evolution of Lisp». History of programming languages---II (PDF). [S.l.: s.n.] pp. 233–330. ISBN 978-0-201-89502-5. doi:10.1145/234286.1057818
  15. O livro de memórias de Herbert A. Simon (1991), Models of My Life pp. 189-190 ISBN 0-465-04640-1 afirma que ele, Al Newell e Cliff Shaw são "...comumente considerados os pais do campo da inteligência artificial", por terem escrito o Logic Theorist, um programa que provava teoremas do Principia Mathematica automaticamente. Para o conseguir, tiveram de inventar uma linguagem e um paradigma que, vistos retrospetivamente, incorporam a programação funcional.
  16. Landin, Peter J. (1964). «The mechanical evaluation of expressions». British Computer Society. The Computer Journal. 6 (4): 308–320. doi:10.1093/comjnl/6.4.308
  17. Diehl, Stephan; Hartel, Pieter; Sestoft, Peter (2000). «Abstract machines for programming language implementation». Future Generation Computer Systems. 16. [S.l.: s.n.] pp. 739–751
  18. Landin, Peter J. (fevereiro de 1965). «Correspondence between ALGOL 60 and Church's Lambda-notation: part I». Association for Computing Machinery. Communications of the ACM. 8 (2): 89–101. doi:10.1145/363744.363749
  19. Landin, Peter J. (março de 1965). «A correspondence between ALGOL 60 and Church's Lambda-notation: part II». Association for Computing Machinery. Communications of the ACM. 8 (3): 158–165. doi:10.1145/363791.363804
  20. Landin, Peter J. (março de 1966). «The next 700 programming languages». Association for Computing Machinery. Communications of the ACM. 9 (3): 157–166. doi:10.1145/365230.365257
  21. Backus, J. (1978). «Can programming be liberated from the von Neumann style?: A functional style and its algebra of programs». Communications of the ACM. 21 (8): 613–641. doi:10.1145/359576.359579
  22. R.M. Burstall. Design considerations for a functional programming language. Invited paper, Proc. Infotech State of the Art Conf. "The Software Revolution", Copenhaga, 45–57 (1977)
  23. R.M. Burstall and J. Darlington. A transformation system for developing recursive programs. Journal of the Association for Computing Machinery 24(1):44–67 (1977)
  24. R.M. Burstall, D.B. MacQueen and D.T. Sannella. HOPE: an experimental applicative language. Proceedings 1980 LISP Conference, Stanford, 136–143 (1980).
  25. «Make discovering assign() easier!». OpenSCAD. Cópia arquivada em 19 de abril de 2023
  26. Bright, Peter (13 de março de 2018). «Developers love trendy new languages but earn more with functional programming». Ars Technica
  27. Leonard, John (24 de janeiro de 2017). «The stealthy rise of functional programming». Computing
  28. Cheung, Leo (9 de maio de 2017). «Is functional programming better for your startup?». InfoWorld
  29. Sean Tull - Monoidal Categories for Formal Concept Analysis.
  30. Pountain, Dick. «Functional Programming Comes of Age». Byte (agosto de 1994). Consultado em 31 de agosto de 2006. Cópia arquivada em 27 de agosto de 2006
  31. 1 2 «ISO/IEC JTC 1/SC 22/WG5/N2137 – Fortran 2015 Committee Draft (J3/17-007r2)» (PDF). International Organization for Standardization. 6 de julho de 2017. pp. 336–338
  32. «Revised^6 Report on the Algorithmic Language Scheme». R6rs.org. Consultado em 21 de março de 2013
  33. «Revised^6 Report on the Algorithmic Language Scheme - Rationale». R6rs.org. Consultado em 21 de março de 2013
  34. Clinger, William (1998). «Proper tail recursion and space efficiency». Proceedings of the ACM SIGPLAN 1998 conference on Programming language design and implementation - PLDI '98. [S.l.: s.n.] pp. 174–185. ISBN 0897919874. doi:10.1145/277650.277719
  35. Baker, Henry (1994). «CONS Should Not CONS Its Arguments, Part II: Cheney on the M.T.A.». Consultado em 29 de abril de 2020. Cópia arquivada em 3 de março de 2006
  36. Turner, D.A. (28 de julho de 2004). «Total Functional Programming». Journal of Universal Computer Science. 10 (7): 751–768. doi:10.3217/jucs-010-07-0751
  37. The Implementation of Functional Programming Languages. Simon Peyton Jones, publicado por Prentice Hall, 1987
  38. Hughes, John (1984). «Why Functional Programming Matters»
  39. 1 2 Launchbury, John (março de 1993). A Natural Semantics for Lazy Evaluation. Symposium on Principles of Programming Languages. Charleston, Carolina do Sul: ACM. pp. 144–154. doi:10.1145/158511.158618
  40. Robert W. Harper (2009). Practical Foundations for Programming Languages (PDF). [S.l.: s.n.] Cópia arquivada (PDF) em 7 de abril de 2016
  41. Huet, Gérard P. (1973). «The Undecidability of Unification in Third Order Logic». Information and Control. 22 (3): 257–267. doi:10.1016/s0019-9958(73)90301-x
  42. Huet, Gérard (setembro de 1976). Resolution d'Equations dans des Langages d'Ordre 1,2,...ω (Ph.D.) (em francês). Universidade Paris VII
  43. Huet, Gérard (2002). «Higher Order Unification 30 years later». In: V. Carreño; C. Muñoz; S. Tahar. Proceedings of the 15th International Conference on Theorem Proving in Higher Order Logics (TPHOLs 2002) (PDF). Col: Lecture Notes in Computer Science. 2410. [S.l.]: Springer. pp. 3–12
  44. Wells, J. B. (1993). «Typability and type checking in the second-order lambda-calculus are equivalent and undecidable». Tech. Rep. 93-011: 176–185
  45. Leroy, Xavier (17 de setembro de 2018). «The Compcert verified compiler»
  46. Peyton Jones, Simon; Vytiniotis, Dimitrios; Weirich, Stephanie; Washburn, Geoffrey (abril de 2006). «Simple unification-based type inference for GADTs». Icfp 2006: 50–61
  47. «OCaml Manual». caml.inria.fr. Consultado em 8 de março de 2021
  48. «Algebraic Data Types». Scala Documentation. Consultado em 8 de março de 2021
  49. Kennedy, Andrew; Russo, Claudio V. (outubro de 2005). Generalized Algebraic Data Types and Object-Oriented Programming. OOPSLA. San Diego, Califórnia: ACM. ISBN 9781595930316. doi:10.1145/1094811.1094814. Cópia arquivada (PDF) em 29 de dezembro de 2006
  50. Hughes, John. «Why Functional Programming Matters» (PDF). Universidade de Tecnologia Chalmers
  51. Purely functional data structures por Chris Okasaki, Cambridge University Press, 1998, ISBN 0-521-66350-4
  52. L’orange, Jean Niklas. «polymatheia - Understanding Clojure's Persistent Vector, pt. 1». Polymatheia. Consultado em 13 de novembro de 2018
  53. Michael Barr, Charles Well - Category theory for computer science.
  54. Newbern, J. «All About Monads: A comprehensive guide to the theory and practice of monadic programming in Haskell». Consultado em 14 de fevereiro de 2008
  55. «Thirteen ways of looking at a turtle». fF# for fun and profit (em inglês). Consultado em 13 de novembro de 2018
  56. Hartmanis, Juris; Hemachandra, Lane (1986). «Complexity classes without machines: On complete languages for UP». Automata, Languages and Programming. Col: Lecture Notes in Computer Science. 226. Berlim, Heidelberg: Springer Berlin Heidelberg. pp. 123–135. ISBN 978-3-540-16761-7. doi:10.1007/3-540-16761-7_62. Consultado em 12 de dezembro de 2024
  57. Paulson, Larry C. (28 de junho de 1996). ML for the Working Programmer. [S.l.]: Cambridge University Press. ISBN 978-0-521-56543-1. Consultado em 10 de fevereiro de 2013
  58. «Which programs are fastest? | Computer Language Benchmarks Game». benchmarksgame.alioth.debian.org. Consultado em 20 de junho de 2011. Cópia arquivada em 20 de maio de 2013
  59. Pechtchanski, Igor; Sarkar, Vivek (2005). «Immutability specification and its applications». Concurrency and Computation: Practice and Experience. 17 (5–6): 639–662. doi:10.1002/cpe.853
  60. «An In-Depth Look at Clojure Collections». InfoQ (em inglês). Consultado em 29 de abril de 2024
  61. «References and Borrowing - The Rust Programming Language». doc.rust-lang.org. Consultado em 29 de abril de 2024
  62. «Validating References with Lifetimes - The Rust Programming Language». doc.rust-lang.org. Consultado em 29 de abril de 2024
  63. «Concurrent Collections (The Java™ Tutorials > Essential Java Classes > Concurrency)». docs.oracle.com. Consultado em 29 de abril de 2024
  64. «Understanding The Actor Model To Build Non-blocking, High-throughput Distributed Systems - Scaleyourapp». scaleyourapp.com (em inglês). 28 de janeiro de 2023. Consultado em 29 de abril de 2024
  65. Cesarini, Francesco; Thompson, Simon (11 de junho de 2009). Erlang programming: a concurrent approach to software development (em inglês) 1 ed. [S.l.]: O'Reilly Media, Inc. p. 6. ISBN 978-0-596-55585-6
  66. «Chapter 25. Profiling and optimization». Book.realworldhaskell.org. Consultado em 20 de junho de 2011
  67. Nethercote, Nicholas; Mycroft, Alan (16 de junho de 2002). «The cache behaviour of large lazy functional programs on stock hardware». ACM SIGPLAN Notices. 38 (2 supplement): 44–55. ISSN 0362-1340. doi:10.1145/773039.773044
  68. Berthe, Samuel (29 de abril de 2024). «samber/lo». Consultado em 29 de abril de 2024
  69. «Go Wiki: Compiler And Runtime Optimizations - The Go Programming Language». go.dev (em inglês). Consultado em 29 de abril de 2024
  70. «Comparing Performance: Loops vs. Iterators - The Rust Programming Language». doc.rust-lang.org. Consultado em 29 de abril de 2024
  71. Hartel, Pieter; Muller, Henk; Glaser, Hugh (março de 2004). «The Functional C experience» (PDF). Journal of Functional Programming. 14 (2): 129–135. doi:10.1017/S0956796803004817. Consultado em 28 de maio de 2006. Cópia arquivada (PDF) em 19 de julho de 2011; Mertz, David. «Functional programming in Python, Part 3». IBM developerWorks. Consultado em 17 de setembro de 2006. Cópia arquivada em 16 de outubro de 2007(Parte 1, Parte 2)
  72. «Functions — D Programming Language 2.0». Digital Mars. 30 de dezembro de 2012
  73. «Lua Unofficial FAQ (uFAQ)»
  74. «First-Class Functions in Go - The Go Programming Language». golang.org. Consultado em 4 de janeiro de 2021
  75. Eich, Brendan (3 de abril de 2008). «Popularity»
  76. van Rossum, Guido (21 de abril de 2009). «Origins of Python's "Functional" Features». Consultado em 27 de setembro de 2012
  77. «functools — Higher order functions and operations on callable objects». Python Software Foundation. 31 de julho de 2011. Consultado em 31 de julho de 2011
  78. Skarsaune, Martin (2008). The SICS Java Port Project Automatic Translation of a Large Object Oriented System from Smalltalk to Java. [S.l.: s.n.]
  79. Gosling, James. «Closures». James Gosling: on the Java Road. Oracle. Consultado em 11 de maio de 2013. Cópia arquivada em 14 de abril de 2013
  80. Williams, Michael (8 de abril de 2013). «Java SE 8 Lambda Quick Start»
  81. Bloch, Joshua (2008). «Item 15: Minimize Mutability». Effective Java Segunda ed. [S.l.]: Addison-Wesley. ISBN 978-0321356680
  82. «Object.freeze() - JavaScript | MDN». developer.mozilla.org. Consultado em 4 de janeiro de 2021
  83. Friedman, Daniel; Byrd, William; Kiselyov, Oleg; Hemann, Jason (2018). The Reasoned Schemer, Second Edition. [S.l.]: The MIT Press
  84. A. Casas, D. Cabeza, M. V. Hermenegildo. A Syntactic Approach to Combining Functional Notation, Lazy Evaluation and Higher-Order in LP Systems. The 8th International Symposium on Functional and Logic Programming (FLOPS'06), paginas 142-162, abril de 2006.
  85. «How I do my Computing». stallman.org. Consultado em 29 de abril de 2024
  86. Wakeling, David (2007). «Spreadsheet functional programming» (PDF). Journal of Functional Programming. 17 (1): 131–143. ISSN 0956-7968. doi:10.1017/S0956796806006186
  87. Peyton Jones, Simon; Burnett, Margaret; Blackwell, Alan (março de 2003). «Improving the world's most popular functional language: user-defined functions in Excel». Cópia arquivada em 16 de outubro de 2005
  88. Rodger, Richard (11 de dezembro de 2017). The Tao of Microservices. [S.l.]: Manning. ISBN 9781638351733
  89. Piro, Christopher (2009). Functional Programming at Facebook. CUFP 2009. Consultado em 29 de agosto de 2009. Cópia arquivada em 17 de outubro de 2009
  90. 1 million is so 2011 Arquivado em 2014-02-19 no Wayback Machine // WhatsApp blog, 2012-01-06: "the last important piece of our infrastracture is Erlang"
  91. Momtahan, Lee (2009). Scala at EDF Trading: Implementing a Domain-Specific Language for Derivative Pricing with Scala. CUFP 2009. Consultado em 29 de agosto de 2009. Cópia arquivada em 17 de outubro de 2009
  92. Graham, Paul (2003). «Beating the Averages». Consultado em 29 de agosto de 2009
  93. Sims, Steve (2006). Building a Startup with Standard ML (PDF). CUFP 2006. Consultado em 29 de agosto de 2009
  94. Laurikari, Ville (2007). Functional Programming in Communications Security. CUFP 2007. Consultado em 29 de agosto de 2009. Cópia arquivada em 21 de dezembro de 2010
  95. Lorimer, R. J. (19 de janeiro de 2009). «Live Production Clojure Application Announced». InfoQ
  96. Bugnion, Pascal (2016). Scala for Data Science (em inglês) 1 ed. [S.l.]: Packt. ISBN 9781785281372
  97. «Why developers like ClojureScript». StackShare (em inglês). Consultado em 29 de abril de 2024
  98. Herrick, Justin (29 de abril de 2024). «jah2488/elm-companies». Consultado em 29 de abril de 2024
  99. «Why developers like PureScript». StackShare (em inglês). Consultado em 29 de abril de 2024
  100. Team, Editorial (8 de janeiro de 2019). «ALLEGRO - all you need to know about the best Polish online marketplace». E-commerce Germany News (em inglês). Consultado em 29 de abril de 2024
  101. «Websites using Phoenix Framework - Wappalyzer». www.wappalyzer.com. Consultado em 29 de abril de 2024
  102. «Functional Programming: 2019-2020». University of Oxford Department of Computer Science. Consultado em 28 de abril de 2020
  103. «Programming I (Haskell)». Imperial College London Department of Computing. Consultado em 28 de abril de 2020
  104. 1 2 «Computer Science BSc - Modules». Consultado em 28 de abril de 2020
  105. 1 2 Abelson, Hal; Sussman, Gerald Jay (1985). «Preface to the Second Edition». Structure and Interpretation of Computer Programs 2 ed. [S.l.]: MIT Press Parâmetro desconhecido |capitulo-url= ignorado (ajuda)
  106. John DeNero (outono de 2019). «Computer Science 61A, Berkeley». Department of Electrical Engineering and Computer Sciences, Berkeley. Consultado em 14 de agosto de 2020
  107. Predefinição:Triangulation
  108. «Why Scheme for Introductory Programming?». home.adelphi.edu. Consultado em 29 de abril de 2024
  109. Staff, IMACS (3 de junho de 2011). «What Is Scheme & Why Is it Beneficial for Students?». IMACS – Making Better Thinkers for Life (em inglês). Consultado em 29 de abril de 2024

Bibliografia

  • Cousineau, Guy and Michel Mauny. The Functional Approach to Programming. Cambridge, UK: Cambridge University Press, 1998.
  • Graham, Paul. ANSI Common LISP. Englewood Cliffs, New Jersey: Prentice Hall, 1996.
  • Pratt, Terrence, W. and Marvin V. Zelkowitz. Programming Languages: Design and Implementation. 3rd ed. Englewood Cliffs, New Jersey: Prentice Hall, 1996.
  • Salus, Peter H. Functional and Logic Programming Languages. Vol. 4 of Handbook of Programming Languages. Indianapolis, Indiana: Macmillan Technical Publishing, 1998.
  • Thompson, Simon. Haskell: The Craft of Functional Programming. Harlow, England: Addison-Wesley Longman Limited, 1996.

Leitura adicional

  • Abelson, Hal; Sussman, Gerald Jay (1985). Structure and Interpretation of Computer Programs. [S.l.]: MIT Press. ISBN 978-0-262-51036-3 
  • Cousineau, Guy; Michel Mauny. The Functional Approach to Programming. Cambridge, Reino Unido: Cambridge University Press, 1998.
  • Curry, Haskell Brooks; Feys, Robert; Craig, William. Combinatory Logic. Volume I. North-Holland Publishing Company, Amesterdão, 1958.
  • Curry, Haskell B.; Hindley, J. Roger; Seldin, Jonathan P. (1972). Combinatory Logic. II. Amesterdão: North Holland. ISBN 978-0-7204-2208-5 
  • Dominus, Mark Jason. Higher-Order Perl. Morgan Kaufmann. 2005.
  • Felleisen, Matthias; Findler, Robert; Flatt, Matthew; Krishnamurthi, Shriram (2018). How to Design Programs. [S.l.]: MIT Press 
  • Graham, Paul. ANSI Common LISP. Englewood Cliffs, Nova Jérsia: Prentice Hall, 1996.
  • MacLennan, Bruce J. Functional Programming: Practice and Theory. Addison-Wesley, 1990.
  • Michaelson, Greg (10 de abril de 2013). An Introduction to Functional Programming Through Lambda Calculus (em inglês). [S.l.]: Courier Corporation. ISBN 978-0-486-28029-5 
  • O'Sullivan, Brian; Stewart, Don; Goerzen, John (2008). Real World Haskell. [S.l.]: O'Reilly 
  • Pratt, Terrence W.; Marvin Victor Zelkowitz. Programming Languages: Design and Implementation. 3.ª ed. Englewood Cliffs, Nova Jérsia: Prentice Hall, 1996.
  • Salus, Peter H. Functional and Logic Programming Languages. Vol. 4 de Handbook of Programming Languages. Indianápolis, Indiana: Macmillan Technical Publishing, 1998.
  • Thompson, Simon. Haskell: The Craft of Functional Programming. Harlow, Inglaterra: Addison-Wesley Longman Limited, 1996.