Ho letto gli articoli di Wikipedia sia per programmazione procedurale e programmazione funzionale , ma io sono ancora un po 'confuso. Qualcuno potrebbe ridurlo fino in fondo?
Ho letto gli articoli di Wikipedia sia per programmazione procedurale e programmazione funzionale , ma io sono ancora un po 'confuso. Qualcuno potrebbe ridurlo fino in fondo?
Risposte:
Un linguaggio funzionale (idealmente) consente di scrivere una funzione matematica, ovvero una funzione che accetta n argomenti e restituisce un valore. Se il programma viene eseguito, questa funzione viene valutata logicamente secondo necessità. 1
Un linguaggio procedurale, d'altra parte, esegue una serie di sequenziali passaggi . (C'è un modo di trasformare la logica sequenziale in logica funzionale chiamata stile di passaggio di continuazione .)
Di conseguenza, un programma puramente funzionale produce sempre lo stesso valore per un input e l'ordine di valutazione non è ben definito; il che significa che valori incerti come l'input dell'utente o valori casuali sono difficili da modellare in linguaggi puramente funzionali.
1 Come tutto il resto in questa risposta, questa è una generalizzazione. Questa proprietà, che valuta un calcolo quando è necessario il suo risultato piuttosto che in sequenza dove viene chiamato, è conosciuta come "pigrizia". Non tutti i linguaggi funzionali sono in realtà universalmente pigri, né la pigrizia è limitata alla programmazione funzionale. Piuttosto, la descrizione fornita qui fornisce un "quadro mentale" per pensare a diversi stili di programmazione che non sono categorie distinte e opposte ma idee piuttosto fluide.
Fondamentalmente i due stili, sono come Yin e Yang. Uno è organizzato, mentre l'altro caotico. Ci sono situazioni in cui la programmazione funzionale è la scelta ovvia e altre situazioni in cui la programmazione procedurale è la scelta migliore. Questo è il motivo per cui ci sono almeno due lingue che sono recentemente uscite con una nuova versione, che abbraccia entrambi gli stili di programmazione. ( 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;
}
(copiato da Wikipedia );
fac :: Integer -> Integer
fac 0 = 1
fac n | n > 0 = n * fac (n-1)
o in una riga:
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 );
}
}
Factorial è in realtà un esempio comune per mostrare quanto sia facile creare nuovi operatori in Perl 6 nello stesso modo in cui si creerebbe una subroutine. Questa funzionalità è così radicata in Perl 6 che la maggior parte degli operatori nell'implementazione di Rakudo è definita in questo modo. Inoltre, consente di aggiungere i propri multi candidati agli operatori esistenti.
sub postfix:< ! > ( UInt:D $n --> UInt )
is tighter(&infix:<*>)
{ [*] 2 .. $n }
say 5!; # 120
Questo esempio mostra anche la creazione dell'intervallo ( 2..$n
) e il meta-operatore di riduzione dell'elenco ( [ OPERATOR ] LIST
) combinato con l'operatore di moltiplicazione numerica dell'infisso. ( *
)
Mostra anche che puoi inserire --> UInt
la firma anziché returns UInt
dopo.
(È possibile cavarsela iniziando l'intervallo con 2
come il moltiplicatore "operatore" tornerà 1
quando chiamato senza alcun argomento)
sub postfix:<!> ($n) { [*] 1..$n }
No operation can have side effects
favore, puoi elaborarlo?
sub foo( $a, $b ){ ($a,$b).pick }
← non restituisce sempre lo stesso output per lo stesso input, mentre ciò che seguesub foo( $a, $b ){ $a + $b }
Non ho mai visto questa definizione data altrove, ma penso che questo riassuma abbastanza bene le differenze qui fornite:
La programmazione funzionale si concentra sulle espressioni
La programmazione procedurale si concentra su dichiarazioni
Le espressioni hanno valori. Un programma funzionale è un'espressione il cui valore è una sequenza di istruzioni da eseguire per il computer.
Le istruzioni non hanno valori e invece modificano lo stato di qualche macchina concettuale.
In un linguaggio puramente funzionale non ci sarebbero dichiarazioni, nel senso che non c'è modo di manipolare lo stato (potrebbero ancora avere un costrutto sintattico chiamato "istruzione", ma a meno che non manipoli lo stato non lo chiamerei un'istruzione in questo senso ). In un linguaggio puramente procedurale non ci sarebbero espressioni, tutto sarebbe un'istruzione che manipola lo stato della macchina.
Haskell sarebbe un esempio di un linguaggio puramente funzionale perché non c'è modo di manipolare lo stato. Il codice macchina sarebbe un esempio di un linguaggio puramente procedurale perché tutto in un programma è un'istruzione che manipola lo stato dei registri e la memoria della macchina.
La parte confusa è che la stragrande maggioranza dei linguaggi di programmazione contiene sia espressioni che dichiarazioni, che consente di mescolare paradigmi. Le lingue possono essere classificate come più funzionali o più procedurali in base a quanto incoraggiano l'uso delle dichiarazioni rispetto alle espressioni.
Ad esempio, C sarebbe più funzionale di COBOL perché una chiamata di funzione è un'espressione, mentre chiamare un sottoprogramma in COBOL è un'istruzione (che modifica lo stato delle variabili condivise e non restituisce un valore). Python sarebbe più funzionale di C perché ti consente di esprimere la logica condizionale come espressione usando la valutazione del corto circuito (test && path1 || path2 al contrario delle istruzioni if). Lo schema sarebbe più funzionale di Python perché tutto nello schema è un'espressione.
Puoi ancora scrivere in uno stile funzionale in una lingua che incoraggi il paradigma procedurale e viceversa. È solo più difficile e / o più imbarazzante scrivere in un paradigma che non è incoraggiato dalla lingua.
Nell'informatica, la programmazione funzionale è un paradigma di programmazione che considera il calcolo come la valutazione di funzioni matematiche ed evita dati di stato e mutabili. Sottolinea l'applicazione di funzioni, in contrasto con lo stile di programmazione procedurale che enfatizza i cambiamenti di stato.
GetUserContext()
la funzione, il contesto dell'utente verrebbe passato. È questa programmazione funzionale? Grazie in anticipo.
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
è una funzione
procedure_to_add_one
è una procedura
Anche se esegui la funzione cinque volte, ogni volta tornerà 2
Se esegui la procedura cinque volte, alla fine della quinta corsa ti darà 6 .
Credo che la programmazione procedurale / funzionale / oggettiva riguardi come affrontare un problema.
Il primo stile pianificherebbe tutto in passaggi e risolve il problema implementando un passaggio (una procedura) alla volta. D'altra parte, la programmazione funzionale enfatizzerebbe l'approccio divide-and-conquer, in cui il problema è suddiviso in sotto-problema, quindi ogni sotto-problema viene risolto (creando una funzione per risolvere quel sotto-problema) e i risultati vengono combinati per creare la risposta per l'intero problema. Infine, la programmazione oggettiva imiterebbe il mondo reale creando un mini-mondo all'interno del computer con molti oggetti, ognuno dei quali ha caratteristiche (un po ') uniche e interagisce con gli altri. Da quelle interazioni il risultato emergerebbe.
Ogni stile di programmazione ha i suoi vantaggi e punti deboli. Quindi, fare qualcosa come "programmazione pura" (cioè puramente procedurale - nessuno lo fa, tra l'altro, che è un po 'strano - o puramente funzionale o puramente oggettivo) è molto difficile, se non impossibile, tranne alcuni problemi elementari appositamente progettato per dimostrare il vantaggio di uno stile di programmazione (da qui, chiamiamo coloro che amano la purezza "weenie": D).
Quindi, da quegli stili, abbiamo linguaggi di programmazione progettati per essere ottimizzati per alcuni stili. Ad esempio, Assembly è interamente procedurale. Va bene, la maggior parte delle prime lingue sono procedurali, non solo Asm, come C, Pascal, (e Fortran, ho sentito). Quindi, abbiamo tutti i famosi Java nella scuola dell'obiettivo (In realtà, anche Java e C # sono in una classe chiamata "orientata al denaro", ma che è argomento per un'altra discussione). Anche l'obiettivo è Smalltalk. Nella scuola funzionale avremmo "quasi funzionale" (alcuni li consideravano impuri) la famiglia Lisp e la famiglia ML e molti "puramente funzionali" Haskell, Erlang, ecc. A proposito, ci sono molte lingue generali come Perl, Python , Ruby.
Per espandere il commento di Konrad:
Di conseguenza, un programma puramente funzionale fornisce sempre lo stesso valore per un input e l'ordine di valutazione non è ben definito;
Per questo motivo, il codice funzionale è generalmente più facile da parallelizzare. Poiché non ci sono (generalmente) effetti collaterali delle funzioni e (generalmente) agiscono solo sui loro argomenti, molti problemi di concorrenza scompaiono.
La programmazione funzionale viene utilizzata anche quando è necessario essere in grado di provare il codice è corretto. Questo è molto più difficile da fare con la programmazione procedurale (non facile con funzionale, ma ancora più semplice).
Dichiarazione di non responsabilità: non utilizzo la programmazione funzionale da anni e solo di recente ho iniziato a esaminarla di nuovo, quindi potrei non essere del tutto corretta qui. :)
Una cosa che non avevo visto enfatizzare qui è che i moderni linguaggi funzionali come Haskell sono molto più sulle funzioni di prima classe per il controllo del flusso che sulla ricorsione esplicita. Non è necessario definire ricorsivamente fattoriale in Haskell, come è stato fatto sopra. Penso a qualcosa del genere
fac n = foldr (*) 1 [1..n]
è una costruzione perfettamente idiomatica, e molto più vicina nello spirito all'uso di un ciclo che all'utilizzo della ricorsione esplicita.
I linguaggi procedurali tendono a tenere traccia dello stato (usando le variabili) e tendono ad essere eseguiti come una sequenza di passaggi. I linguaggi puramente funzionali non tengono traccia dello stato, usano valori immutabili e tendono ad essere eseguiti come una serie di dipendenze. In molti casi lo stato dello stack di chiamate conterrà le informazioni che sarebbero equivalenti a quelle che verrebbero archiviate nelle variabili di stato nel codice procedurale.
La ricorsione è un classico esempio di programmazione di stile funzionale.
Konrad ha detto:
Di conseguenza, un programma puramente funzionale fornisce sempre lo stesso valore per un input e l'ordine di valutazione non è ben definito; il che significa che valori incerti come l'input dell'utente o valori casuali sono difficili da modellare in linguaggi puramente funzionali.
L'ordine di valutazione in un programma puramente funzionale può essere difficile (o) ragionare (specialmente con la pigrizia) o addirittura non importante, ma penso che dire che non è ben definito sembra che non si possa dire se il programma sta andando lavorare a tutti!
Forse una spiegazione migliore sarebbe che il flusso di controllo nei programmi funzionali si basa su quando è necessario il valore degli argomenti di una funzione. La cosa buona di questo che in programmi ben scritti lo stato diventa esplicito: ogni funzione elenca i suoi input come parametri invece di mungere arbitrariamente lo stato globale. Quindi, ad un certo livello, è più facile ragionare sull'ordine di valutazione rispetto a una funzione alla volta . Ogni funzione può ignorare il resto dell'universo e concentrarsi su ciò che deve fare. Se combinate, le funzioni sono garantite per funzionare allo stesso modo [1] come se fossero isolate.
... valori incerti come input dell'utente o valori casuali sono difficili da modellare in linguaggi puramente funzionali.
La soluzione al problema di input in programmi puramente funzionali è incorporare un linguaggio imperativo come DSL usando un'astrazione sufficientemente potente . Nei linguaggi imperativi (o non puri funzionali) questo non è necessario perché puoi "imbrogliare" e passare lo stato implicitamente e l'ordine di valutazione è esplicito (che ti piaccia o no). A causa di questo "imbroglio" e della valutazione forzata di tutti i parametri per ogni funzione, nei linguaggi imperativi 1) si perde la capacità di creare i propri meccanismi di flusso di controllo (senza macro), 2) il codice non è intrinsecamente thread-safe e / o parallelizzabile per impostazione predefinita, 3) e l'implementazione di qualcosa come annulla (viaggio nel tempo) richiede un lavoro accurato (il programmatore imperativo deve memorizzare una ricetta per recuperare i vecchi valori!), Mentre la pura programmazione funzionale ti compra tutte queste cose - e alcune altre potrei ho dimenticato ... "gratis".
Spero che questo non sembri uno zelo, volevo solo aggiungere qualche prospettiva. La programmazione imperativa e in particolare la programmazione a paradigma misto in linguaggi potenti come C # 3.0 sono ancora modi totalmente efficaci per fare le cose e non esiste un proiettile d'argento .
[1] ... tranne forse per quanto riguarda l'utilizzo della memoria (cfr. Foldl e foldl 'in Haskell).
Per espandere il commento di Konrad:
e l'ordine di valutazione non è ben definito
Alcuni linguaggi funzionali hanno quella che viene chiamata valutazione pigra. Ciò significa che una funzione non viene eseguita fino a quando non è necessario il valore. Fino a quel momento la funzione stessa è ciò che viene trasmesso.
I linguaggi procedurali sono passaggio 1 passaggio 2 passaggio 3 ... se nel passaggio 2 si dice di aggiungere 2 + 2, lo fa subito. Nella valutazione pigra diresti di aggiungere 2 + 2, ma se il risultato non viene mai usato, non fa mai l'aggiunta.
Se ne hai la possibilità, consiglierei di ottenere una copia di Lisp / Scheme e di farci alcuni progetti. La maggior parte delle idee che recentemente sono diventate bandwagon sono state espresse in Lisp decenni fa: programmazione funzionale, continuazioni (come chiusure), garbage collection, persino XML.
Quindi sarebbe un buon modo per ottenere un vantaggio su tutte queste idee attuali e alcune altre, come il calcolo simbolico.
Dovresti sapere a cosa serve la programmazione funzionale ea cosa non serve. Non va bene per tutto. Alcuni problemi si esprimono meglio in termini di effetti collaterali, in cui la stessa domanda fornisce risposte diverse a seconda di quando viene posta.
@Creighton:
In Haskell c'è una funzione di libreria chiamata product :
prouduct list = foldr 1 (*) list
o semplicemente:
product = foldr 1 (*)
quindi il fattoriale "idiomatico"
fac n = foldr 1 (*) [1..n]
sarebbe semplicemente
fac n = product [1..n]
La programmazione procedurale divide sequenze di istruzioni e costrutti condizionali in blocchi separati chiamati procedure parametrizzate su argomenti che sono valori (non funzionali).
La programmazione funzionale è la stessa tranne per il fatto che le funzioni sono valori di prima classe, quindi possono essere passate come argomenti ad altre funzioni e restituite come risultati dalle chiamate di funzione.
Si noti che la programmazione funzionale è una generalizzazione della programmazione procedurale in questa interpretazione. Tuttavia, una minoranza interpreta la "programmazione funzionale" nel senso libero da effetti collaterali, che è piuttosto diverso ma irrilevante per tutti i principali linguaggi funzionali tranne Haskell.
Per capire la differenza, è necessario capire che il paradigma "il padrino" della programmazione procedurale e funzionale è la programmazione imperativa .
Fondamentalmente la programmazione procedurale è semplicemente un modo di strutturare programmi imperativi in cui il metodo principale di astrazione è la "procedura". (o "funzione" in alcuni linguaggi di programmazione). Anche la programmazione orientata agli oggetti è solo un altro modo di strutturare un programma imperativo, in cui lo stato è incapsulato in oggetti, diventando un oggetto con uno "stato corrente", più questo oggetto ha un insieme di funzioni, metodi e altre cose che ti permettono il programmatore manipola o aggiorna lo stato.
Ora, per quanto riguarda la programmazione funzionale, l' essenza del suo approccio è che identifica quali valori prendere e come questi valori dovrebbero essere trasferiti. (quindi non c'è stato e nessun dato mutabile in quanto assume funzioni come valori di prima classe e le passa come parametri ad altre funzioni).
PS: la comprensione di ogni paradigma di programmazione utilizzato dovrebbe chiarire le differenze tra tutti loro.
PSS: Alla fine della giornata, i paradigmi di programmazione sono solo approcci diversi per risolvere i problemi.
PSS: questa risposta a quora ha una grande spiegazione.
Nessuna delle risposte qui mostra una programmazione funzionale idiomatica. La risposta fattoriale ricorsiva è ottima per rappresentare la ricorsione in FP, ma la maggior parte del codice non è ricorsiva, quindi non credo che la risposta sia pienamente rappresentativa.
Supponi di avere una matrice di stringhe e ciascuna stringa rappresenta un numero intero come "5" o "-200". Si desidera verificare questo array di input di stringhe con il proprio caso di test interno (utilizzando il confronto di numeri interi). Entrambe le soluzioni sono mostrate di seguito
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
Mentre i linguaggi funzionali puri sono generalmente linguaggi di ricerca (poiché al mondo reale piacciono gli effetti collaterali gratuiti), i linguaggi procedurali del mondo reale useranno la sintassi funzionale molto più semplice quando appropriato.
Questo di solito è implementato con una libreria esterna come Lodash o disponibile integrato con linguaggi più recenti come Rust . Il lavoro pesante di programmazione funzionale avviene con funzioni / concetti come map
, filter
, reduce
, currying
,partial
, gli ultimi tre dei quali si può guardare in alto per una maggiore comprensione.
Per essere utilizzato in natura, il compilatore dovrà normalmente capire come convertire internamente la versione funzionale nella versione procedurale, poiché l'overhead della chiamata di funzione è troppo elevato. Casi ricorsivi come il fattoriale mostrato useranno trucchi come la coda per rimuovere l'uso della memoria O (n). Il fatto che non vi siano effetti collaterali consente ai compilatori funzionali di implementare l' && ret
ottimizzazione anche quando .reduce
è stato eseguito per ultimo. L'uso di Lodash in JS, ovviamente, non consente alcuna ottimizzazione, quindi è un successo per le prestazioni (che di solito non riguarda lo sviluppo web). Linguaggi come Rust ottimizzeranno internamente (e hanno funzioni tali da try_fold
favorire l' && ret
ottimizzazione).