Compreender verdadeiramente a diferença entre processual e funcional

114

Estou realmente tendo dificuldade em entender a diferença entre os paradigmas de programação procedural e funcional .

Aqui estão os dois primeiros parágrafos da entrada da Wikipedia sobre programação funcional :

Na ciência da computação, a programação funcional é um paradigma de programação que trata a computação como a avaliação de funções matemáticas e evita estados e dados mutáveis. Ele enfatiza a aplicação de funções, em contraste com o estilo de programação imperativo, que enfatiza as mudanças no estado. A programação funcional tem suas raízes no cálculo lambda, um sistema formal desenvolvido na década de 1930 para investigar a definição de função, sua aplicação e recursão. Muitas linguagens de programação funcional podem ser vistas como elaborações do cálculo lambda.

Na prática, a diferença entre uma função matemática e a noção de uma "função" usada na programação imperativa é que as funções imperativas podem ter efeitos colaterais, alterando o valor do estado do programa. Por isso, eles carecem de transparência referencial, ou seja, a mesma expressão de linguagem pode resultar em valores diferentes em momentos diferentes, dependendo do estado do programa em execução. Por outro lado, no código funcional, o valor de saída de uma função depende apenas dos argumentos de entrada para a função, portanto, chamar uma função fduas vezes com o mesmo valor para um argumento xproduzirá o mesmo resultadof(x)ambas as vezes. A eliminação dos efeitos colaterais pode tornar muito mais fácil entender e prever o comportamento de um programa, o que é uma das principais motivações para o desenvolvimento da programação funcional.

No parágrafo 2, onde diz

Por outro lado, no código funcional, o valor de saída de uma função depende apenas dos argumentos de entrada para a função, portanto, chamar uma função fduas vezes com o mesmo valor para um argumento xproduzirá o mesmo resultado nas f(x)duas vezes.

Não é exatamente o mesmo caso para programação procedural?

O que se deve procurar em procedural vs funcional que se destaque?

Philoxopher
fonte
1
O link "Charming Python: Functional Programming in Python" do Abafei foi quebrado. Aqui está um bom conjunto de links: ibm.com/developerworks/linux/library/l-prog/index.html ibm.com/developerworks/linux/library/l-prog2/index.html
Chris Koknat
Outro aspecto disso é a nomenclatura. Por exemplo. em JavaScript e Common Lisp, usamos o termo função, embora sejam permitidos efeitos colaterais, e em Scheme ele é consistentemente chamado de proceduere. Uma função CL que é pura pode ser escrita como um procedimento Scheme funcional puro. Quase todos os livros sobre Scheme usam o termo procedimento, pois é o tyerm usado no padrão e não tem nada a ver com ser procedural ou funcional.
Sylwester,

Respostas:

276

Programação Funcional

A programação funcional se refere à capacidade de tratar funções como valores.

Vamos considerar uma analogia com valores "regulares". Podemos pegar dois valores inteiros e combiná-los usando o +operador para obter um novo inteiro. Ou podemos multiplicar um inteiro por um número de ponto flutuante para obter um número de ponto flutuante.

Na programação funcional, podemos combinar dois valores de função para produzir um novo valor de função usando operadores como compor ou elevador . Ou podemos combinar um valor de função e um valor de dados para produzir um novo valor de dados usando operadores como map ou fold .

Observe que muitas linguagens têm recursos de programação funcional - até mesmo linguagens que geralmente não são consideradas linguagens funcionais. Até mesmo o avô FORTRAN suportava valores de função, embora não oferecesse muito em termos de operadores de combinação de funções. Para uma linguagem ser chamada de "funcional", ela precisa abraçar amplamente os recursos de programação funcional.

Programação Processual

A programação procedural refere-se à capacidade de encapsular uma sequência comum de instruções em um procedimento, de forma que essas instruções possam ser chamadas de muitos lugares sem recorrer ao copiar e colar. Como os procedimentos foram um desenvolvimento muito inicial na programação, a capacidade está quase invariavelmente ligada ao estilo de programação exigido pela programação em linguagem de máquina ou assembly: um estilo que enfatiza a noção de locais de armazenamento e instruções que movem dados entre esses locais.

Contraste

Os dois estilos não são realmente opostos - eles são apenas diferentes um do outro. Existem linguagens que abraçam totalmente os dois estilos (LISP, por exemplo). O cenário a seguir pode dar uma ideia de algumas diferenças entre os dois estilos. Vamos escrever um código para um requisito sem sentido em que queremos determinar se todas as palavras em uma lista têm um número ímpar de caracteres. Primeiro, estilo de procedimento:

function allOdd(words) {
  var result = true;
  for (var i = 0; i < length(words); ++i) {
    var len = length(words[i]);
    if (!odd(len)) {
      result = false;
      break;
    }
  }
  return result;
}

Vou considerar que este exemplo é compreensível. Agora, estilo funcional:

function allOdd(words) {
  return apply(and, map(compose(odd, length), words));
}

Trabalhando de dentro para fora, esta definição faz o seguinte:

  1. compose(odd, length)combina as funções odde lengthpara produzir uma nova função que determina se o comprimento de uma string é ímpar.
  2. map(..., words)chama essa nova função para cada elemento em words, em última análise, retornando uma nova lista de valores booleanos, cada um indicando se a palavra correspondente tem um número ímpar de caracteres.
  3. apply(and, ...)aplica o operador "e" à lista resultante e -ing todos os booleanos juntos para produzir o resultado final.

Você pode ver a partir desses exemplos que a programação procedural está muito preocupada em mover valores nas variáveis ​​e em descrever explicitamente as operações necessárias para produzir o resultado final. Em contraste, o estilo funcional enfatiza a combinação de funções necessárias para transformar a entrada inicial na saída final.

O exemplo também mostra os tamanhos relativos típicos de código procedural versus código funcional. Além disso, demonstra que as características de desempenho do código procedural podem ser mais fáceis de ver do que as do código funcional. Considere: as funções calculam os comprimentos de todas as palavras na lista ou cada uma para imediatamente após encontrar a primeira palavra de comprimento par? Por outro lado, o código funcional permite uma implementação de alta qualidade para realizar algumas otimizações bastante sérias, uma vez que expressa principalmente a intenção em vez de um algoritmo explícito.

Leitura Adicional

Essa dúvida surge muito ... veja, por exemplo:

A palestra do prêmio Turing de John Backus explica as motivações para a programação funcional em grandes detalhes:

A programação pode ser libertada do estilo de von Neumann?

Eu realmente não deveria mencionar esse artigo no presente contexto porque ele se torna muito técnico, muito rapidamente. Eu simplesmente não pude resistir porque acho que é verdadeiramente fundamental.


Adendo - 2013

Os comentaristas apontam que as linguagens contemporâneas populares oferecem outros estilos de programação além de procedimental e funcional. Essas linguagens geralmente oferecem um ou mais dos seguintes estilos de programação:

  • consulta (por exemplo, compreensões de lista, consulta com linguagem integrada)
  • fluxo de dados (por exemplo, iteração implícita, operações em massa)
  • orientado a objetos (por exemplo, dados e métodos encapsulados)
  • orientado para a linguagem (por exemplo, sintaxe específica do aplicativo, macros)

Veja os comentários abaixo para exemplos de como os exemplos de pseudocódigo nesta resposta podem se beneficiar de alguns dos recursos disponíveis desses outros estilos. Em particular, o exemplo de procedimento se beneficiará da aplicação de virtualmente qualquer construção de nível superior.

Os exemplos exibidos evitam deliberadamente a mistura desses outros estilos de programação para enfatizar a distinção entre os dois estilos em discussão.

WReach
fonte
1
De fato, boa resposta, mas você poderia simplificar um pouco o código, por exemplo: "função allOdd (palavras) {foreach (palavra automática em palavras) {ímpar (comprimento (palavra)? Retorno falso:;} retorno verdadeiro;}"
Dainius
O estilo funcional lá é muito difícil de ler em comparação com o "estilo funcional" em python: def odd_words (words): return [x para x em palavras se ímpar (len (x))]
encaixotado em
@boxed: Sua odd_words(words)definição faz algo diferente da resposta allOdd. Para filtragem e mapeamento, as compreensões de lista são freqüentemente preferidas, mas aqui a função allOdddeve reduzir uma lista de palavras a um único valor booleano.
ShinNoNoir
@WReach: Eu teria escrito seu exemplo funcional assim: function allOdd (palavras) {return and (odd (length (first (words))), allOdd (rest (words))); } Não é mais elegante do que o seu exemplo, mas em uma linguagem recursiva de cauda teria as mesmas características de desempenho do estilo imperativo.
mishoo
@mishoo A linguagem precisa ser recursiva e estrita e em curto-circuito no operador e para que sua suposição seja válida, eu acredito.
kqr
46

A diferença real entre programação funcional e imperativa é a mentalidade - os programadores imperativos estão pensando em variáveis ​​e blocos de memória, enquanto os programadores funcionais estão pensando: "Como posso transformar meus dados de entrada em dados de saída" - seu "programa" é o pipeline e um conjunto de transformações nos dados para levá-los da entrada para a saída. Essa é a parte interessante da IMO, não a parte "Não usarás variáveis".

Como consequência dessa mentalidade, os programas de PF geralmente descrevem o que vai acontecer, em vez do mecanismo específico de como isso vai acontecer - isso é poderoso porque se pudermos afirmar claramente o que significa "Selecionar" e "Onde" e "Agregar", nós estão livres para trocar suas implementações, assim como fazemos com AsParallel () e, de repente, nosso aplicativo de thread único é dimensionado para n núcleos.

Ana betts
fonte
alguma maneira você pode contrastar os dois usando trechos de exemplo de código? realmente aprecio isso
Philoxopher
1
@KerxPhilo: Aqui está uma tarefa muito simples (adicione números de 1 a n). Imperativo: Modifique o número atual, modifique a soma até agora. Código: int i, sum; soma = 0; para (i = 1; i <= n; i ++) {soma + = i; } Funcional (Haskell): pegue uma lista preguiçosa de números, dobre-os juntos enquanto soma zero. Código: foldl (+) 0 [1..n]. Desculpe, sem formatação nos comentários.
dirkt
+1 para a resposta. Em outras palavras, a programação funcional é sobre escrever funções sem efeitos colaterais sempre que possível, ou seja, a função sempre retorna a mesma coisa quando dados os mesmos parâmetros - essa é a base. Se você seguir essa abordagem ao extremo, seus efeitos colaterais (você sempre precisa deles) serão isolados e o resto das funções simplesmente transformam os dados de entrada em dados de saída.
beluchin
12
     Isn't that the same exact case for procedural programming?

Não, porque o código de procedimento pode ter efeitos colaterais. Por exemplo, ele pode armazenar o estado entre as chamadas.

Dito isso, é possível escrever código que satisfaça esta restrição em linguagens consideradas procedurais. E também é possível escrever código que quebre essa restrição em algumas linguagens consideradas funcionais.

Andy Thomas
fonte
1
Você pode mostrar um exemplo e comparação? Agradeço verdadeiramente se puder.
Filoxofo
8
A função rand () em C fornece um resultado diferente para cada chamada. Ele armazena o estado entre as chamadas. Não é referencialmente transparente. Em comparação, o std :: max (a, b) em C ++ sempre retornará o mesmo resultado com os mesmos argumentos e não tem efeitos colaterais (que eu saiba ...).
Andy Thomas,
11

Não concordo com a resposta de WReach. Vamos desconstruir um pouco sua resposta para ver de onde vem a discordância.

Primeiro, seu código:

function allOdd(words) {
  var result = true;
  for (var i = 0; i < length(words); ++i) {
    var len = length(words[i]);
    if (!odd(len)) {
      result = false;
      break;
    }
  }
  return result;
}

e

function allOdd(words) {
  return apply(and, map(compose(odd, length), words));
}

A primeira coisa a notar é que ele está confundindo:

  • Funcional
  • Orientado para a expressão e
  • Iterator centric

programação e sem a capacidade de programação de estilo iterativo para ter um fluxo de controle mais explícito do que um estilo funcional típico.

Vamos falar rapidamente sobre isso.

O estilo centrado na expressão é aquele em que as coisas, tanto quanto possível, são avaliadas em relação às coisas. Embora as linguagens funcionais sejam famosas por seu amor por expressões, na verdade é possível ter uma linguagem funcional sem expressões composíveis. Vou inventar um, onde não haja expressões, apenas afirmações.

lengths: map words length
each_odd: map lengths odd
all_odd: reduce each_odd and

É praticamente igual ao fornecido antes, exceto que as funções são encadeadas puramente por meio de cadeias de instruções e ligações.

Um estilo de programação centrado em iterador pode ser aquele adotado pelo Python. Vamos usar um estilo puramente iterativo, centrado no iterador:

def all_odd(words):
    lengths = (len(word) for word in words)
    each_odd = (odd(length) for length in lengths)
    return all(each_odd)

Isso não é funcional, porque cada cláusula é um processo iterativo e são unidas por pausa explícita e retomada dos quadros de pilha. A sintaxe pode ser inspirada parcialmente em uma linguagem funcional, mas é aplicada a uma incorporação completamente iterativa dela.

Claro, você pode compactar isso:

def all_odd(words):
    return all(odd(len(word)) for word in words)

O imperativo não parece tão ruim agora, hein? :)

O ponto final foi sobre um fluxo de controle mais explícito. Vamos reescrever o código original para fazer uso disso:

function allOdd(words) {
    for (var i = 0; i < length(words); ++i) {
        if (!odd(length(words[i]))) {
            return false;
        }
    }
    return true;
}

Usando iteradores, você pode ter:

function allOdd(words) {
    for (word : words) { if (!odd(length(word))) { return false; } }
    return true;
}

Então, qual é o objetivo de uma linguagem funcional se a diferença é entre:

return all(odd(len(word)) for word in words)
return apply(and, map(compose(odd, length), words))
for (word : words) { if (!odd(length(word))) { return false; } }
return true;


A principal característica definitiva de uma linguagem de programação funcional é que ela remove a mutação como parte do modelo de programação típico. Muitas vezes as pessoas entendem que isso significa que uma linguagem de programação funcional não tem instruções ou usa expressões, mas essas são simplificações. Uma linguagem funcional substitui a computação explícita por uma declaração de comportamento, na qual a linguagem então realiza uma redução.

Restringir-se a este subconjunto de funcionalidade permite que você tenha mais garantias sobre o comportamento de seus programas e isso permite que você os componha com mais liberdade.

Quando você tem uma linguagem funcional, criar novas funções geralmente é tão simples quanto compor funções intimamente relacionadas.

all = partial(apply, and)

Isso não é simples, ou talvez nem seja possível, se você não controlou explicitamente as dependências globais de uma função. A melhor característica da programação funcional é que você pode criar consistentemente abstrações mais genéricas e confiar que elas podem ser combinadas em um todo maior.

Veedrac
fonte
Você sabe, tenho quase certeza de que an applynão é exatamente a mesma operação que foldou reduce, embora concorde com a boa capacidade de ter algoritmos muito genéricos.
Benedict Lee,
Eu nunca ouvi falar de applysignificar foldou reduce, mas parece-me que ele tem que ser neste contexto para que possa retornar um boolean.
Veedrac
Ah, ok, fiquei confuso com a nomenclatura. Obrigado por esclarecer isso.
Benedict Lee,
6

No paradigma procedural (devo dizer "programação estruturada" em vez disso?), Você compartilhou memória mutável e instruções que a lêem / escrevem em alguma seqüência (uma após a outra).

No paradigma funcional, você tem variáveis ​​e funções (no sentido matemático: as variáveis ​​não variam ao longo do tempo, as funções só podem computar algo com base em suas entradas).

(Isso é muito simplificado, por exemplo, os FPLs geralmente têm recursos para trabalhar com memória mutável, enquanto as linguagens procedurais podem muitas vezes suportar procedimentos de ordem superior, então as coisas não são tão claras; mas isso deve dar uma ideia)

Artyom Shalkhakov
fonte
2

The Charming Python: A programação funcional em Python da IBM Developerworks realmente me ajudou a entender a diferença.

Especialmente para alguém que conhece um pouco Python, os exemplos de código neste artigo nos quais fazer coisas diferentes funcional e proceduralmente são contrastados, podem esclarecer a diferença entre programação procedural e funcional.

Abbafei
fonte
2

Na programação funcional, a fim de raciocinar sobre o significado de um símbolo (variável ou nome de função), você só precisa saber realmente 2 coisas - o escopo atual e o nome do símbolo. Se você tiver uma linguagem puramente funcional com imutabilidade, ambos são conceitos "estáticos" (desculpe pelo nome muito sobrecarregado), o que significa que você pode ver ambos - o escopo atual e o nome - apenas olhando o código-fonte.

Na programação procedural, se você quiser responder à pergunta qual é o valor por trás, xvocê também precisa saber como você chegou lá, o escopo e o nome por si só não são suficientes. E isso é o que eu veria como o maior desafio porque esse caminho de execução é uma propriedade "runtime" e pode depender de tantas coisas diferentes, que a maioria das pessoas aprende apenas a depurar e não a tentar recuperar o caminho de execução.

vasily
fonte
1

Recentemente, estive pensando na diferença em termos do Problema de Expressão . A descrição de Phil Wadler é freqüentemente citada, mas a resposta aceita para essa pergunta é provavelmente mais fácil de seguir. Basicamente, parece que as linguagens imperativas tendem a escolher uma abordagem para o problema, enquanto as linguagens funcionais tendem a escolher a outra.

John L
fonte
0

Uma diferença clara entre os dois paradigmas de programação é o estado.

Na programação funcional, o estado é evitado. Simplificando, não haverá nenhuma variável com um valor atribuído.

Exemplo:

def double(x):
    return x * 2

def doubleLst(lst):
    return list(map(double, action))

No entanto, a programação procedural usa o estado.

Exemplo:

def doubleLst(lst):
    for i in range(len(lst)):
        lst[i] = lst[i] * 2  # assigning of value i.e. mutation of state
    return lst
Tox
fonte