Qual è la differenza tra programmazione procedurale e programmazione funzionale? [chiuso]


247

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?


Wikipedia implica che FP è un sottoinsieme della programmazione dichiarativa (cioè è sempre), ma ciò non è vero e fonde la tassonomia di IP vs. DP .
Shelby Moore III,

Risposte:


151

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.


9
Valori incerti come input dell'utente o valori casuali sono difficili da modellare in linguaggi puramente funzionali, ma questo è un problema risolto. Vedi monadi.
Apocalisp,

" passi sequenziali , in cui il programma funzionale sarebbe annidato" significa prevedere la separazione delle preoccupazioni enfatizzando la composizione della funzione , cioè separando le dipendenze tra le sottocomputer di un calcolo deterministico.
Shelby Moore III,

questo sembra sbagliato - le procedure possono anche essere nidificate, le procedure possono avere parametri
Hurda,

1
@Hurda Sì, avrebbe potuto formularlo meglio. Il punto è che la programmazione procedurale avviene in modo graduale in un ordine predeterminato, mentre i programmi funzionali non vengono eseguiti in modo graduale; piuttosto, i valori vengono calcolati quando sono necessari. Tuttavia, la mancanza di una definizione generalmente concordata della terminologia di programmazione rende tali generalizzazioni quasi inutili. Ho modificato la mia risposta al riguardo.
Konrad Rudolph,

97

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 )

Procedurale:

  • L'output di una routine non ha sempre una correlazione diretta con l'input.
  • Tutto è fatto in un ordine specifico.
  • L'esecuzione di una routine può avere effetti collaterali.
  • Tende a enfatizzare l'implementazione di soluzioni in modo lineare.

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;
}

Funzionale:

  • Spesso ricorsivo.
  • Restituisce sempre lo stesso output per un determinato input.
  • L'ordine di valutazione è generalmente indefinito.
  • Deve essere apolide. cioè nessuna operazione può avere effetti collaterali.
  • Adatta per l'esecuzione parallela
  • Tende a enfatizzare un approccio di divisione e conquista.
  • Potrebbe avere la funzione di Lazy Evaluation.

Haskell

(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

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 a margine:

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 --> UIntla firma anziché returns UIntdopo.

(È possibile cavarsela iniziando l'intervallo con 2come il moltiplicatore "operatore" tornerà 1quando chiamato senza alcun argomento)


Ciao, puoi fornire un esempio per i seguenti 2 punti menzionati per "Procedurale" considerando l'esempio dell'implementazione fattoriale in Perl 6. 1) L'output di una routine non ha sempre una correlazione diretta con l'input. 2) L'esecuzione di una routine può avere effetti collaterali.
Naga Kiran,

sub postfix:<!> ($n) { [*] 1..$n }
Brad Gilbert,

@BradGilbert -Per No operation can have side effectsfavore, puoi elaborarlo?
kushalvm,

2
Probabilmente la migliore risposta che io abbia mai trovato .... E, ho fatto qualche ricerca su quei singoli punti .. questo mi ha davvero aiutato! :)
Navaneeth,

1
@AkashBisariya 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 }
Brad Gilbert

70

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.


2
Spiegazione migliore e più concisa che ho visto sul web, bravo!
tommed

47

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.


4
Mentre questa è la spiegazione che mi ha aiutato di più, sono ancora confuso sul concetto di programmazione funzionale. Sto cercando uno stile di programmazione che non dipenda dal riferimento a oggetti esterni per essere eseguito (ogni cosa che la funzione deve essere eseguita deve essere passata come parametro). Ad esempio, non inserirò mai GetUserContext()la funzione, il contesto dell'utente verrebbe passato. È questa programmazione funzionale? Grazie in anticipo.
Matt Cashatt,

27

Programmazione funzionale

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

Programmazione procedurale

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 .


5
questo esempio è davvero semplice da comprendere il termine "apolide" e "dati immutabili" nella programmazione funzionale, leggere tutte le definizioni e le differenze sopra elencate non ha chiarito la mia confusione fino a quando non ho letto questa risposta. Grazie!
maximus

26

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.


13

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


12

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.


10

Una programmazione funzionale è identica alla programmazione procedurale in cui le variabili globali non vengono utilizzate.


7

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.


1
Dopo aver letto questa pagina stavo pensando alla stessa cosa -> "La ricorsione è un classico esempio di programmazione di stile funzionale", e tu l'hai cancellata. Grazie, ora penso che sto ricevendo qualcosa.
Mudassir Hussain,

6

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


5

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.


4

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.


3

@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]

Questo non fornisce una risposta alla domanda. Per criticare o richiedere chiarimenti a un autore, lascia un commento sotto il suo post.
Nick Kitto

Credo che questo sia stato pubblicato molti anni fa, prima che fosse aggiunto il sistema dei commenti, se ci credete: stackoverflow.com/help/badges/30/beta?userid=2543
Jared Updike

2

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.


1

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.


0

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

Procedurale

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;
}

Funzionale

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.

appendice

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' && retottimizzazione 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_foldfavorire l' && retottimizzazione).

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.