Qual é a diferença entre programação procedural e programação funcional? [fechadas]

247

Eu li os artigos da Wikipedia sobre programação procedural e programação funcional , mas ainda estou um pouco confuso. Alguém poderia resumir tudo?

Thomas Owens
fonte
A Wikipedia implica que FP é um subconjunto de (ou seja, é sempre) programação declarativa, mas isso não é verdade e conflita a taxonomia de IP versus DP .
Shelby Moore III

Respostas:

151

Uma linguagem funcional (idealmente) permite que você escreva uma função matemática, ou seja, uma função que recebe n argumentos e retorna um valor. Se o programa for executado, essa função será avaliada logicamente conforme necessário. 1

Uma linguagem processual, por outro lado, executa uma série de etapas seqüenciais . (Existe uma maneira de transformar a lógica seqüencial em lógica funcional chamada estilo de passagem de continuação .)

Como conseqüência, um programa puramente funcional sempre gera o mesmo valor para uma entrada e a ordem da avaliação não é bem definida; o que significa que valores incertos, como entrada do usuário ou valores aleatórios, são difíceis de modelar em linguagens puramente funcionais.


1 Como tudo nesta resposta, isso é uma generalização. Essa propriedade, avaliando uma computação quando seu resultado é necessário, e não sequencialmente onde é chamada, é conhecida como "preguiça". Nem todas as linguagens funcionais são realmente preguiçosas universalmente, nem a preguiça é restrita à programação funcional. Em vez disso, a descrição fornecida aqui fornece uma "estrutura mental" para pensar em diferentes estilos de programação que não são categorias distintas e opostas, mas idéias fluidas.

Konrad Rudolph
fonte
9
Valores incertos, como entrada do usuário ou valores aleatórios, são difíceis de modelar em linguagens puramente funcionais, mas esse é um problema resolvido. Veja mônadas.
Apocalisp
" etapas seqüenciais , onde o programa funcional seria aninhado" significa fornecer separação de preocupações enfatizando a composição da função , isto é, separando as dependências entre as subcomputações de uma computação determinística.
Shelby Moore III
isso parece errado - procedimentos também podem ser aninhados, os procedimentos podem ter parâmetros
Hurda
1
@ Hurda Sim, poderia ter formulado isso melhor. O ponto é que a programação procedural acontece passo a passo em uma ordem pré-determinada, enquanto os programas funcionais não são executados passo a passo; em vez disso, os valores são calculados quando necessários. No entanto, a falta de uma definição de terminologia de programação geralmente acordada torna essas generalizações quase inúteis. Eu alterei minha resposta a esse respeito.
Konrad Rudolph
97

Basicamente, os dois estilos, são como Yin e Yang. Um é organizado, enquanto o outro é caótico. Há situações em que a programação funcional é a escolha óbvia e outras situações em que a programação procedural é a melhor escolha. É por isso que há pelo menos duas linguagens lançadas recentemente com uma nova versão, que abrange os dois estilos de programação. ( Perl 6 e D 2 )

Procedimental:

  • A saída de uma rotina nem sempre tem uma correlação direta com a entrada.
  • Tudo é feito em uma ordem específica.
  • A execução de uma rotina pode ter efeitos colaterais.
  • Tende a enfatizar a implementação de soluções de maneira linear.

Perl 6

sub factorial ( UInt:D $n is copy ) returns UInt {

  # modify "outside" state
  state $call-count++;
  # in this case it is rather pointless as
  # it can't even be accessed from outside

  my $result = 1;

  loop ( ; $n > 0 ; $n-- ){

    $result *= $n;

  }

  return $result;
}

D 2

int factorial( int n ){

  int result = 1;

  for( ; n > 0 ; n-- ){
    result *= n;
  }

  return result;
}

Funcional:

  • Frequentemente recursivo.
  • Sempre retorna a mesma saída para uma determinada entrada.
  • A ordem da avaliação é geralmente indefinida.
  • Deve ser apátrida. ou seja, nenhuma operação pode ter efeitos colaterais.
  • Bom ajuste para execução paralela
  • Tende a enfatizar uma abordagem de dividir e conquistar.
  • Pode ter o recurso de Lazy Evaluation.

Haskell

(copiado da Wikipedia );

fac :: Integer -> Integer

fac 0 = 1
fac n | n > 0 = n * fac (n-1)

ou em uma linha:

fac n = if n > 0 then n * fac (n-1) else 1

Perl 6

proto sub factorial ( UInt:D $n ) returns UInt {*}

multi sub factorial (  0 ) { 1 }
multi sub factorial ( $n ) { $n * samewith $n-1 } # { $n * factorial $n-1 }

D 2

pure int factorial( invariant int n ){
  if( n <= 1 ){
    return 1;
  }else{
    return n * factorial( n-1 );
  }
}

Nota:

O fatorial é realmente um exemplo comum para mostrar como é fácil criar novos operadores no Perl 6 da mesma maneira que você criaria uma sub-rotina. Esse recurso está tão arraigado no Perl 6 que a maioria dos operadores na implementação do Rakudo é definida dessa maneira. Também permite adicionar seus próprios candidatos múltiplos aos operadores existentes.

sub postfix:< ! > ( UInt:D $n --> UInt )
  is tighter(&infix:<*>)
  { [*] 2 .. $n }

say 5!; # 120␤

Este exemplo também mostra a criação de intervalo ( 2..$n) e o meta-operador de redução de lista ( [ OPERATOR ] LIST) combinado com o operador de multiplicação de infixos numéricos. ( *)
Também mostra que você pode colocar --> UInta assinatura em vez de returns UIntdepois dela.

(Você pode começar o intervalo com 2o "operador de multiplicação" retornando 1quando chamado sem argumentos)

Brad Gilbert
fonte
Oi, você pode fornecer um exemplo para os 2 pontos a seguir mencionados para "Procedural", considerando o exemplo de implementação fatorial no Perl 6. 1) A saída de uma rotina nem sempre tem uma correlação direta com a entrada. 2) A execução de uma rotina pode ter efeitos colaterais.
Naga Kiran
sub postfix:<!> ($n) { [*] 1..$n }
23320 Brad Gilbert
@BradGilbert No operation can have side effects-Pode elaborar isso?
21412 kushalvm
2
Provavelmente a melhor resposta que eu já encontrei ... E fiz algumas pesquisas sobre esses pontos individuais ... que realmente me ajudaram! :)
Navaneeth
1
@AkashBisariya sub foo( $a, $b ){ ($a,$b).pick }← nem sempre retorna a mesma saída para a mesma entrada, enquanto que a seguir fazsub foo( $a, $b ){ $a + $b }
Brad Gilbert
70

Eu nunca vi essa definição dada em outro lugar, mas acho que isso resume muito bem as diferenças apresentadas aqui:

A programação funcional foca em expressões

A programação processual se concentra nas declarações

Expressões têm valores. Um programa funcional é uma expressão cujo valor é uma sequência de instruções para o computador executar.

As instruções não têm valores e, em vez disso, modificam o estado de alguma máquina conceitual.

Em uma linguagem puramente funcional, não haveria declarações, no sentido de que não há como manipular o estado (elas ainda podem ter um construto sintático chamado "declaração", mas, a menos que manipule o estado, eu não chamaria de declaração nesse sentido. ) Em uma linguagem puramente processual, não haveria expressões, tudo seria uma instrução que manipula o estado da máquina.

Haskell seria um exemplo de uma linguagem puramente funcional porque não há como manipular o estado. O código da máquina seria um exemplo de uma linguagem puramente processual, porque tudo em um programa é uma declaração que manipula o estado dos registros e a memória da máquina.

A parte confusa é que a grande maioria das linguagens de programação contêm ambas expressões e declarações, o que lhe permite misturar paradigmas. Os idiomas podem ser classificados como mais funcionais ou mais processuais, com base no quanto encorajam o uso de declarações versus expressões.

Por exemplo, C seria mais funcional que COBOL porque uma chamada de função é uma expressão, enquanto chamar um subprograma em COBOL é uma instrução (que manipula o estado de variáveis ​​compartilhadas e não retorna um valor). O Python seria mais funcional que o C porque permite que você expresse a lógica condicional como uma expressão usando a avaliação de curto-circuito (teste && path1 || path2 em oposição às instruções if). O esquema seria mais funcional que o Python, porque tudo no esquema é uma expressão.

Você ainda pode escrever em um estilo funcional em uma linguagem que incentive o paradigma processual e vice-versa. É apenas mais difícil e / ou mais estranho escrever em um paradigma que não é incentivado pela linguagem.

Omnimike
fonte
2
A melhor e mais sucinta explicação que eu já vi na web, bravo!
Tommed
47

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 dados de estado e mutáveis. Ele enfatiza a aplicação de funções, em contraste com o estilo de programação procedural que enfatiza as mudanças de estado.

juan
fonte
4
Embora essa seja a explicação que mais me ajudou, ainda estou confuso com o conceito de programação funcional. Estou procurando um estilo de programação que não dependa de objetos externos de referência para executar (tudo que a função precisa executar deve ser passado como parâmetro). Por exemplo, eu nunca colocaria GetUserContext()a função, o contexto do usuário seria passado. Essa programação é funcional? Desde já, obrigado.
Matt Cashatt
26

Acredito que a programação processual / funcional / objetiva seja sobre como abordar um problema.

O primeiro estilo planejaria tudo em etapas e resolveria o problema implementando uma etapa (um procedimento) por vez. Por outro lado, a programação funcional enfatizaria a abordagem de dividir e conquistar, onde o problema é dividido em subproblema, então cada subproblema é resolvido (criando uma função para resolver esse subproblema) e os resultados são combinados para crie a resposta para todo o problema. Por fim, a programação objetiva imitaria o mundo real, criando um mini-mundo dentro do computador com muitos objetos, cada um com características (um tanto) únicas e interagindo com outros. A partir dessas interações, o resultado emergiria.

Cada estilo de programação tem suas próprias vantagens e fraquezas. Portanto, fazer algo como "programação pura" (ou seja, puramente processual - a propósito, ninguém faz isso, o que é meio estranho - ou puramente funcional ou puramente objetivo) é muito difícil, se não impossível, exceto alguns problemas elementares, especialmente projetado para demonstrar a vantagem de um estilo de programação (por isso, chamamos aqueles que gostam de pureza "weenie": D).

Então, a partir desses estilos, temos linguagens de programação projetadas para serem otimizadas para alguns estilos. Por exemplo, Assembly é tudo sobre procedimentos. Ok, a maioria das linguagens antigas é processual, não apenas Asm, como C, Pascal (e Fortran, eu ouvi dizer). Então, todos nós temos Java famoso na escola objetiva (na verdade, Java e C # também estão em uma classe chamada "orientada a dinheiro", mas isso está sujeito a outra discussão). Também objetivo é Smalltalk. Na escola funcional, teríamos "quase funcional" (alguns consideram impuros) a família Lisp e a família ML e muitos Haskell, Erlang, etc. "puramente funcionais". A propósito, existem muitas linguagens gerais, como Perl, Python. Ruby.

Aaron Hall
fonte
26

Programação Funcional

num = 1 
def function_to_add_one(num):
    num += 1
    return num


function_to_add_one(num)
function_to_add_one(num)
function_to_add_one(num)
function_to_add_one(num)
function_to_add_one(num)

#Final Output: 2

Programação processual

num = 1 
def procedure_to_add_one():
    global num
    num += 1
    return num


procedure_to_add_one()
procedure_to_add_one()
procedure_to_add_one()
procedure_to_add_one()
procedure_to_add_one()

#Final Output: 6

function_to_add_one é uma função

procedure_to_add_one é um procedimento

Mesmo se você executar a função cinco vezes, sempre que ela retornar 2

Se você executar o procedimento cinco vezes, no final da quinta execução, ele fornecerá 6 .

Hamza Zubair
fonte
5
esse exemplo é realmente simples de entender o termo "sem estado" e "dados imutáveis" na programação funcional, a leitura de todas as definições e diferenças listadas acima não esclareceu minha confusão até a leitura desta resposta. Obrigado!
Maximus
13

Para expandir o comentário de Konrad:

Como conseqüência, um programa puramente funcional sempre gera o mesmo valor para uma entrada e a ordem da avaliação não é bem definida;

Por esse motivo, geralmente é mais fácil paralelizar o código funcional. Como (geralmente) não existem efeitos colaterais das funções e elas (geralmente) agem de acordo com seus argumentos, muitos problemas de concorrência desaparecem.

A programação funcional também é usada quando você precisa provar que seu código está correto. Isso é muito mais difícil de fazer com a programação procedural (não é fácil com o funcional, mas ainda é mais fácil).

Isenção de responsabilidade: eu não uso programação funcional há anos e só recentemente comecei a analisá-la novamente, por isso talvez não esteja completamente correto aqui. :)

Herms
fonte
12

Uma coisa que eu não tinha visto realmente enfatizado aqui é que linguagens funcionais modernas, como Haskell, realmente se dedicam mais às funções de primeira classe para controle de fluxo do que recursão explícita. Você não precisa definir fatorial recursivamente em Haskell, como foi feito acima. Eu acho algo como

fac n = foldr (*) 1 [1..n]

é uma construção perfeitamente idiomática e muito mais próxima do espírito de usar um loop do que de recursão explícita.

C Hogg
fonte
10

Uma programação funcional é idêntica à programação procedural na qual variáveis ​​globais não estão sendo usadas.

Nir O.
fonte
7

Linguagens procedurais tendem a acompanhar o estado (usando variáveis) e a executar como uma sequência de etapas. Linguagens puramente funcionais não controlam o estado, usam valores imutáveis ​​e tendem a executar como uma série de dependências. Em muitos casos, o status da pilha de chamadas manterá as informações equivalentes às que seriam armazenadas nas variáveis ​​de estado no código processual.

A recursão é um exemplo clássico de programação de estilo funcional.

Cunha
fonte
1
Depois de ler esta página, pensei na mesma coisa -> "A recursão é um exemplo clássico de programação de estilos funcionais" e você a limpou. Obrigado, agora acho que estou conseguindo alguma coisa.
Mudassir Hussain
6

Konrad disse:

Como conseqüência, um programa puramente funcional sempre gera o mesmo valor para uma entrada e a ordem da avaliação não é bem definida; o que significa que valores incertos, como entrada do usuário ou valores aleatórios, são difíceis de modelar em linguagens puramente funcionais.

A ordem da avaliação em um programa puramente funcional pode ser difícil de raciocinar (principalmente com preguiça) ou até sem importância, mas acho que dizer que não está bem definido faz parecer que você não pode dizer se o seu programa está indo para trabalhar em tudo!

Talvez uma explicação melhor seja que o fluxo de controle em programas funcionais se baseie em quando o valor dos argumentos de uma função é necessário. A coisa boa sobre isso que em programas bem escritos, estado torna-se explícita: cada função enumera suas entradas como parâmetros em vez de arbitrariamente munging estado global. Portanto, em algum nível, é mais fácil argumentar sobre a ordem da avaliação com relação a uma função por vez . Cada função pode ignorar o resto do universo e se concentrar no que precisa fazer. Quando combinadas, é garantido que as funções funcionem da mesma forma que [1] como isolariam.

... valores incertos como entrada do usuário ou valores aleatórios são difíceis de modelar em linguagens puramente funcionais.

A solução para o problema de entrada em programas puramente funcionais é incorporar uma linguagem imperativa como DSL usando uma abstração suficientemente poderosa . Em linguagens imperativas (ou funcionais não puras), isso não é necessário porque você pode "trapacear" e passar o estado implicitamente e a ordem da avaliação é explícita (se você gosta ou não). Devido a essa "trapaça" e avaliação forçada de todos os parâmetros para todas as funções, em linguagens imperativas 1) você perde a capacidade de criar seus próprios mecanismos de fluxo de controle (sem macros), 2) o código não é inerentemente seguro por thread e / ou paralelamente agradável por padrão, 3) e implementar algo como desfazer (viagem no tempo) exige um trabalho cuidadoso (o programador imperativo deve armazenar uma receita para recuperar os valores antigos!), Enquanto a programação funcional pura compra todas essas coisas - e mais algumas esqueceu - "de graça".

Espero que isso não pareça zelo, apenas queria acrescentar uma perspectiva. Programação imperativa e programação de paradigma especialmente mista em linguagens poderosas como o C # 3.0 ainda são maneiras totalmente eficazes de fazer as coisas e não há uma bala de prata .

[1] ... exceto possivelmente com relação ao uso de memória (cf. foldl e foldl 'em Haskell).

Jared Updike
fonte
5

Para expandir o comentário de Konrad:

e a ordem da avaliação não está bem definida

Algumas linguagens funcionais têm o que é chamado de Avaliação Preguiçosa. O que significa que uma função não é executada até que o valor seja necessário. Até esse momento, a função em si é o que é transmitido.

Linguagens procedurais são etapa 1 etapa 2 etapa 3 ... se na etapa 2 você diz adicionar 2 + 2, faz isso exatamente então. Na avaliação preguiçosa, você diria adicionar 2 + 2, mas se o resultado nunca for usado, nunca fará a adição.

Brian Leahy
fonte
4

Se você tiver uma chance, eu recomendaria obter uma cópia do Lisp / Scheme e fazer alguns projetos nela. A maioria das idéias que ultimamente se tornaram bandwagons foram expressas no Lisp décadas atrás: programação funcional, continuações (como fechamentos), coleta de lixo e até XML.

Portanto, essa seria uma boa maneira de começar com todas essas idéias atuais e mais algumas, como a computação simbólica.

Você deve saber para que serve a programação funcional e para que não serve. Não é bom para tudo. Alguns problemas são melhor expressos em termos de efeitos colaterais, onde a mesma pergunta fornece respostas diferentes, dependendo de quando é solicitada.

Mike Dunlavey
fonte
3

@Creighton:

No Haskell, existe uma função de biblioteca chamada product :

prouduct list = foldr 1 (*) list

ou simplesmente:

product = foldr 1 (*)

então o fatorial "idiomático"

fac n = foldr 1 (*)  [1..n]

seria simplesmente

fac n = product [1..n]
Jared Updike
fonte
Isso não fornece uma resposta para a pergunta. Para criticar ou solicitar esclarecimentos a um autor, deixe um comentário abaixo da postagem.
quer
Eu acredito que este foi publicado há muitos anos, antes que o sistema comentário foi adicionado, se você pode acreditar: stackoverflow.com/help/badges/30/beta?userid=2543
Jared Updike
2

A programação procedural divide seqüências de instruções e construções condicionais em blocos separados chamados procedimentos que são parametrizados sobre argumentos que são valores (não funcionais).

A programação funcional é a mesma, exceto que as funções são valores de primeira classe; portanto, elas podem ser passadas como argumentos para outras funções e retornadas como resultados de chamadas de função.

Observe que a programação funcional é uma generalização da programação procedural nesta interpretação. No entanto, uma minoria interpreta "programação funcional" como livre de efeitos colaterais, o que é bastante diferente, mas irrelevante para todas as principais linguagens funcionais, exceto Haskell.

Jon Harrop
fonte
1

Para entender a diferença, é preciso entender que o paradigma "padrinho" da programação processual e funcional é a programação imperativa .

Basicamente, a programação procedural é apenas uma maneira de estruturar programas imperativos nos quais o principal método de abstração é o "procedimento". (ou "função" em algumas linguagens de programação). Mesmo a Programação Orientada a Objetos é apenas outra maneira de estruturar um programa imperativo, em que o estado é encapsulado em objetos, tornando-se um objeto com um "estado atual", mais esse objeto tem um conjunto de funções, métodos e outras coisas que permitem que você programador manipular ou atualizar o estado.

Agora, no que diz respeito à programação funcional, a essência de sua abordagem é que ela identifica quais valores tomar e como esses valores devem ser transferidos. (portanto, não há estado nem dados mutáveis, pois ele funciona como valores de primeira classe e os passa como parâmetros para outras funções).

PS: entender que cada paradigma de programação é usado deve esclarecer as diferenças entre todos eles.

PSS: No final das contas, os paradigmas de programação são apenas abordagens diferentes para resolver problemas.

PSS: essa resposta do quora tem uma ótima explicação.

Fouad Boukredine
fonte
0

Nenhuma das respostas aqui mostra programação funcional idiomática. A resposta fatorial recursiva é ótima para representar recursão no FP, mas a maioria do código não é recursiva, então não acho que essa resposta seja totalmente representativa.

Digamos que você tenha matrizes de cadeias de caracteres e cada sequência represente um número inteiro como "5" ou "-200". Você deseja verificar essa matriz de entrada de seqüências de caracteres em relação ao seu caso de teste interno (usando comparação de números inteiros). Ambas as soluções são mostradas abaixo

Procedural

arr_equal(a : [Int], b : [Str]) -> Bool {
    if(a.len != b.len) {
        return false;
    }

    bool ret = true;
    for( int i = 0; i < a.len /* Optimized with && ret*/; i++ ) {
        int a_int = a[i];
        int b_int = parseInt(b[i]);
        ret &= a_int == b_int;  
    }
    return ret;
}

Funcional

eq = i, j => i == j # This is usually a built-in
toInt = i => parseInt(i) # Of course, parseInt === toInt here, but this is for visualization

arr_equal(a : [Int], b : [Str]) -> Bool =
    zip(a, b.map(toInt)) # Combines into [Int, Int]
   .map(eq)
   .reduce(true, (i, j) => i && j) # Start with true, and continuously && it with each value

Embora as linguagens funcionais puras sejam geralmente linguagens de pesquisa (como o mundo real gosta de efeitos colaterais gratuitos), as linguagens procedurais do mundo real usarão a sintaxe funcional muito mais simples, quando apropriado.

Isso geralmente é implementado com uma biblioteca externa como o Lodash , ou disponível embutido em idiomas mais novos, como o Rust . O trabalho pesado da programação funcional é feito com funções / conceitos como map, filter, reduce, currying, partial, os três últimos dos quais você pode olhar para cima para uma maior compreensão.

Termo aditivo

Para ser usado na natureza, normalmente o compilador precisa descobrir como converter a versão funcional na versão procedural internamente, pois a sobrecarga da chamada da função é muito alta. Casos recursivos, como o fatorial mostrado, usarão truques como chamada de cauda para remover o uso de memória de O (n). O fato de não haver efeitos colaterais permite que os compiladores funcionais implementem a && retotimização mesmo quando o .reduceúltimo for feito. O uso do Lodash no JS, obviamente, não permite nenhuma otimização, portanto é um impacto no desempenho (o que geralmente não é uma preocupação com o desenvolvimento da web). Idiomas como o Rust otimizarão internamente (e terão funções como try_foldauxiliar na && retotimização).

Nicholas Pipitone
fonte