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?
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?
Respostas:
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.
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 )
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;
}
int factorial( int n ){
int result = 1;
for( ; n > 0 ; n-- ){
result *= n;
}
return result;
}
(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
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 }
pure int factorial( invariant int n ){
if( n <= 1 ){
return 1;
}else{
return n * factorial( n-1 );
}
}
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 --> UInt
a assinatura em vez de returns UInt
depois dela.
(Você pode começar o intervalo com 2
o "operador de multiplicação" retornando 1
quando chamado sem argumentos)
sub postfix:<!> ($n) { [*] 1..$n }
No operation can have side effects
-Pode elaborar isso?
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 }
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.
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.
GetUserContext()
a função, o contexto do usuário seria passado. Essa programação é funcional? Desde já, obrigado.
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.
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
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 .
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. :)
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.
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.
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).
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.
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.
@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]
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.
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.
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
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;
}
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.
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 && ret
otimizaçã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_fold
auxiliar na && ret
otimização).